benjaminmayo / merchantkit

A modern In-App Purchases management framework for iOS.
MIT License
1.11k stars 72 forks source link

Question about the auto-renewable subscription. #58

Closed hellbit closed 4 years ago

hellbit commented 4 years ago

When I check the state of the subscription it returns case .isPurchased(_): But my subscription was expired and I don't have any subscriptions right now.

The question is: Do I need to check the info.expiryDate from case .isPurchased(_):? I am asking it because I see this one in the code - Note: Do not use this value to decide whether to allow access to the product.

And another question is: why do I see two different dates? From iTunes I see 11 Jul 2020 but info.expiryDate is equal 2020-06-26 10:39:48 +0000

This's my code:

private init() {
        configurateStoreKit()
    }

    private func configurateStoreKit() {
        self.merchant = Merchant.init(configuration: .default, delegate: self)
        self.merchant.register(ProductDatabase.allProducts)
        self.merchant.setup()
        self.checkAvailablePurchases()
    }

    private  func checkAvailablePurchases() {
        self.task = merchant.availablePurchasesTask(for: ProductDatabase.allProducts)
        self.task?.onCompletion = { [weak self] result in
            switch result {
            case .success(let value):
                DispatchQueue.main.async {
                    self?.products = value.sortedByPrice(ascending: true)
                    self?.checkSubscription()
                }
            case .failure(_): break
            }
        }
        self.task?.start()
    }
 func checkSubscription() {
        let monthState = self.merchant.state(for: ProductDatabase.monthlySubscription)
        let sixmonthlyState = self.merchant.state(for: ProductDatabase.sixmonthlySubscription)
        let yearState = self.merchant.state(for: ProductDatabase.yearlySubscription)

        switch monthState {
            case .isPurchased(_):
                self.setPurchase(true)
                return;
            default: break
        }

        switch sixmonthlyState {
        case .isPurchased(_):
            self.setPurchase(true)
            return;
        default: break
        }

        switch yearState {
        case .isPurchased(_):
            self.setPurchase(true)
            return;
        default:
            break
        }

        self.setPurchase(false)
    }

extension SubscriptionServices: MerchantDelegate {

    func merchant(_ merchant: Merchant, didChangeStatesFor products: Set<Product>) {
        checkSubscription()
    }

    /// Called when the `isLoading` property on the `Merchant` changes. You may want to update UI in response to loading state changes, e.g. show/hide the status bar network activity indicator, or you may want to do nothing. The default implementation of this delegate method does nothing.
    func merchantDidChangeLoadingState(_ merchant: Merchant) {

    }

    /// Called when a user activates a Promoted In-App Purchase in the App Store, with the intent to buy the `Product`. The default implementation of this delegate method returns `StoreIntentResponse.default` (equal to `StoreIntentResponse.automaticallyCommit`) which begins the purchase flow immediately. You may want to defer the commit until later, in which case your application logic should keep hold of the `Purchase` to use later, and return `StoreIntentResponse.defer`.
    func merchant(_ merchant: Merchant, didReceiveStoreIntentToCommit purchase: Purchase) -> StoreIntentResponse {
        return .default
    }

}

Thank you

benjaminmayo commented 4 years ago

There is sometime a lag between StoreKit updating renewals and the actual state of subscriptions. MerchantKit offers a grace period to avoid these issues and prevents users being told they need to pay, when they are actually paying. The info represents the last known value, which is why it will sometimes be out of sync. You should only use isPurchased to determine whether to give access to the user's product.

If you want to, you can customize the duration of the grace period by providing a custom configuration, but I wouldn't recommend it.

yildirimatcioglu commented 3 years ago

My canceled subscriptions returning as isPurcahsed true up to 1 months delay. What can I do about it?

benjaminmayo commented 3 years ago

As explained above, you can supply a custom configuration to the Merchant(...) initialiser, which customises the timeout via the subscriptionRenewalLeeway on the LocalReceiptValidator. However, I don't recommend it as it penalises legitimate users.

yildirimatcioglu commented 3 years ago

subscriptionRenewalLeeway is which value in default configuration?