3

My app has a 1 month autorenewal subscription. When the user clicks on a "Buy a subscription" button I am saving date of purchase to shared preferences. Then, after 1 month, I need to check is this subscription is still valid. So how can I implement it?

Timur
  • 111
  • 1
  • 11
  • Please elaborate your question in order to receive a good response. It is too general. – Carlos Cavero Feb 27 '19 at 18:34
  • I found these info on the official site, maybe helps somebody. Handling Subscriptions Billing https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/handling_subscriptions_billing Validating Receipts with the App Store https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/offering_a_subscription_across_multiple_apps – Alexandru Rusu Feb 03 '21 at 07:35
  • and for Android: https://developer.android.com/google/play/billing/subscriptions – Alexandru Rusu Feb 03 '21 at 07:42
  • This package https://pub.dev/packages/flutter_inapp_purchase#-readme-tab- seems to have a method getAvailablePurchases that offers all purchases made by the user (either non-consumable, or haven't been consumed yet) – Alexandru Rusu Feb 03 '21 at 07:49

3 Answers3

13

==== UPDATE from 11.03.2020

Hi, I can see this post still reading by people who looking for a method of how to work with subscription in Flutter. During 2019 I made two apps with thousands installs where users can buy a renewable subscription on the 2 platforms. Until February 2020 I used for this package from Flutter team https://pub.dev/packages/in_app_purchase, BUT - there is no way to get info about the user to unsubscribe in iOS. This is not the plugin issue, but the iOS approach for the process. We should implement our own backend for security reasons (by the way Google also recommends to do the same, but still left the way to check state directly from the app).

So, after some researches, I found guys who made backend and plugin and it is free until you have less than 10 000 USD revenue for the month. https://www.revenuecat.com/ https://pub.dev/packages/purchases_flutter

I've implemented this plugin in my apps and it works like a charm. There is some good approaches that allow you to get a subscription state at any point in the app. I'm going to make an example and article, but not sure about the timing.

====

UPDATE from 15.07.2019. Just to save time. The answer below was given for an outdated plugin for payments. After that Flutter team made plugin https://pub.dev/packages/in_app_purchase and I recommend using it.

=====

The best way is to use a secure backend server for receiving Real-time Developer Notifications. But, it is possible to check status directly in the application. So, when user tries to get access to some paid functionality you can check whether his subscription is active or not. Below is the example:

Create somewhere the file with the class

import 'dart:io' show Platform;
import 'package:flutter/services.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
import 'dart:async';

class SubcsriptionStatus {
static Future<bool> subscriptionStatus(
  String sku,
  [Duration duration = const Duration(days: 30),
  Duration grace = const Duration(days: 0)]) async {
    if (Platform.isIOS) {
      var history = await FlutterInappPurchase.getPurchaseHistory();

      for (var purchase in history) {
        Duration difference =
        DateTime.now().difference(purchase.transactionDate);
        if (difference.inMinutes <= (duration + grace).inMinutes &&
            purchase.productId == sku) return true;
      }
      return false;
    } else if (Platform.isAndroid) {
      var purchases = await FlutterInappPurchase.getAvailablePurchases();

      for (var purchase in purchases) {
        if (purchase.productId == sku) return true;
      }
      return false;
    }
    throw PlatformException(
        code: Platform.operatingSystem, message: "platform not supported");
  }
}

Import it where you need to check subscription status and use in Constructor. For example:

class _SubscriptionState extends State<Subscription> {
  bool userSubscribed;
  _SubscriptionState() {
  SubcsriptionStatus.subscriptionStatus(iapId, const Duration(days: 30), const 
  Duration(days: 0)).then((val) => setState(() {
  userSubscribed = val;
   }));
   }
}

In variable userSubscribed will be the state - true or false. (Please note you have to add flutter_inapp_purchase to your project).

awaik
  • 10,143
  • 2
  • 44
  • 50
  • Hey there, quick question, I implemented your class and it seems to work just fine. I am just wondering, are subscriptions capped to 30 days or is a "monthly basis" sometimes 31 days, sometimes 30 and one time 28? Because your example will only work if the subscription is expiring after 30 days right? – Frederik Witte May 08 '19 at 19:31
  • Hi! Cool :) As you can see from code with Android you don't really need this "Duration(days: 30)" - they make billing and give answer whether subscription is paid or not. But for IOS you have to handle it on your side. So, I guess, you should check current month and use 28(29), 30 or 31 days. Just add method for this. Sorry, right now I'm busy, can do it next week. – awaik May 09 '19 at 13:55
  • Ah okay, I understand. I'm just not sure how IOS handles it, if you buy it in February if the product will be bought for 28/29 days only then for example.. – Frederik Witte May 09 '19 at 14:29
  • Hey awaik, I checked now and indeed the iOS system handles it differently, it's not 30 days but rather one month, like 4/30 to 5/29 – Frederik Witte May 16 '19 at 10:20
  • Ahh, thank you for the info, I will implement for iOS within few days in my app also. – awaik May 17 '19 at 12:23
  • Hey Frederick, finally I released iOS app. So it is possible to check state of subscription without month day calculation. Something like code below https://gist.github.com/awaik/7806273546b6a901d8691b355a927fac – awaik Jul 20 '19 at 07:55
  • So it’s possible out of the box in this new plugin? Ah sorry Wrote to soon! Thanks! – Frederik Witte Jul 20 '19 at 07:56
  • Hi @awaik thanks for this answer. I start to design a flutter app. That app has content that will only come available to payed (month of year subscriptions). For that do i need something like revenuecat.com or can i check that status in the app? And,how to connect je authenticated (firebase) to the subscription. If the user is logged (firebase authentication) in how do i now he is a payed customer? – jr00n Sep 24 '20 at 05:38
  • Hi @jr00n The firebase authentication and checking subscription status don't connect between them. For example, it is possible to make the app that doesn't need authorization, but, have the subscription and functionality that user can get one after pay. For that, you need one check subscribed user or not (RevenueCat makes it easier and faster to implement). If the app hasn't free functionality, you can make it paid from the AppStore without any coding. The user will pay when he installs the app. In this case, you don't need either RevenueCat or authentication. – awaik Sep 24 '20 at 06:35
  • @awaik, thanks for the reply. I think i understand. In my case i have audio content in Firebase (firestore) which i will reveal to payed (subscription) customers. So to the user need to be authenticated to Firebase, so the content can be downloaded by that user within the app. Or is this a stupid idea? So there is free content (also in firestore) and there is content fot which a customer must have a subscription. How to expose that content in a Flutter app? That is my quest at the moment. Do you have any ideas? Thanks!! – jr00n Sep 25 '20 at 07:11
  • @jr00n It is a good idea. You can give free access without authorization and show inactive premium content. When the user subscribed, ask him to login after subscription, and, then, give access to premium content. It is quite straightforward logic and not so difficult to implement. Of course, if you are new with Flutter, it takes more time, but it is the learning time, enjoy it :) Maybe next week or to I made ready to use example app, but for now, I have a project to finish. – awaik Sep 25 '20 at 07:33
  • Thanks @awaik that would be great to see in a demo app. – jr00n Sep 30 '20 at 13:30
  • https://docs.revenuecat.com/discuss/5ea749abbbfeff0018f166f3 I am facing this issue. Please help. I can pay you. – Chirag Chopra Nov 09 '20 at 09:11
  • maybe the reason is "The Play Store credentials take 24hrs to propagate through Google, so if you've just set them up you'll unfortunately need to wait at least a day before making purchases." – awaik Nov 09 '20 at 09:20
  • I have an app that can be accessed with a subscription. I have in mind to implement two ways of payments. One via the app's website (using) stripe and Firebase, the other one based on in-app-purchase. The fact is that I don't have a clear view of how to manage the subscription state. I consider recurring subscriptions, so that stripe can renew the subscription state, while Apple/Google can do the same as well. Is there away to request the subscription state and how it should be done?Also, I'm thinking for a method to check the subscription state when the device is not connected to the internet – Alexandru Rusu Feb 02 '21 at 18:06
  • @GeorgeAlex I think in this case the best approach is to implement all functionality with your own backend. After that, the app and the website will not depend on one payment provider. In case you have a lot of subscribers it can be great because you can avoid markets commissions and get payments directly. – awaik Feb 03 '21 at 06:44
  • @awaik Unfortunately, payments for digital goods especially in iOS is not possible anyway and only using in-app-purchase. For Android the policy is almost the same, but I identified some apps that are redirecting the user to their own payments provider (i.e., Spotify for Android). I don't have time and resources to implement my own payment provider, that is why I would like to use Stripe for payments through the website and in-app-purchase for mobile. I would like to avoid the 30% cut, but at the same the time to offer the opportunity for lower price, as many companies do. – Alexandru Rusu Feb 03 '21 at 07:13
  • @awaik Do you have any clue about an article that describes my concerns from the previous comments? – Alexandru Rusu Feb 03 '21 at 07:18
  • @GeorgeAlex I can recommend try this plugin https://pub.dev/packages/stripe_payment and make UI where you offer the user 2 prices: 1. big price with AppStore 2. with discount with stripe After that, you can use the selected payment method and keep this method in the user setting. So, when you need to check that subscription is still valid - you just use the appropriate checking method. – awaik Feb 03 '21 at 07:43
  • @awaik As far as I see, stripe_payment package offers the native pay option, so this is great for iOS. Do you think I can use stripe as direct gateway to avoid the 30% cut from Android? Take in consideration that I enable digital goods. – Alexandru Rusu Feb 03 '21 at 07:55
  • @George Alex you are required by the app store to use their API for IAP when selling digital goods. – Max Tromp May 16 '21 at 18:33
  • @MaxTromp thank you, I already had a deep look and realized that it can't be avoided in this way. – Alexandru Rusu May 17 '21 at 08:21
3

There's a few ways of doing this, but I would not do this on the mobile device.

On Device like you asked for

Install Flutter Cache Manager, on start set a cache key value 'Subscription' to true with maxAgeCacheObject: Duration (days: 30). On every start check if that key still exists in the cache. If it does then it's still valid otherwise it has expired.

Suggested solution using FirebaseFunction

I would suggest setting up a backend to manage all this. This is not a task for the mobile device. You can have a Cloud Function from firebase where you pass a unique device id and it'll return whether the subscription is still valid or not. A serverless function should work for that. Pseudo steps:

  1. (On Device)When the app starts up generate a guid and make an http post request with your guid.
  2. (Server)In your serverless function save the date the request is made to your db along with the uniqueId that you sent. If your id is already in the DB then check if it's expired (date added - current date) < 30days. Return true or false from the function. True if still valid, false if not valid.
  3. (On Device) When you receive true from your function then save the generated id locally on disk and continue with what you want to do. If it's false then lock the user out or show the subscription that you want to take care of.
Community
  • 1
  • 1
Filled Stacks
  • 4,116
  • 1
  • 23
  • 36
  • A server-side solution is the correct way to implement this. Without this there are numerous vulnerabilities like 1) fake receipt 2) getting a refund after purchase 3) changing system time to continue subscription. and more. What's missing here is checking the receipt more often than 30-days to see if it was cancelled/refunded. – enc_life Feb 28 '19 at 02:10
  • @enc_life I agree. I mentioned "When the app starts up ..." so this will happen in every session. You'll make the request every session. If true is returned then you continue, otherwise you block the user. Maybe even do a lock down so you don't have to do the request again until the perform your subscribe logic and complete it. – Filled Stacks Feb 28 '19 at 03:22
  • Yeah you're right about checking the status on every app startup. I was referring to re-verifying the receipt server-side periodically to see if the user was refunded before the scheduled expiration date and you need to cut off access early. – enc_life Feb 28 '19 at 05:54
  • @enc_life I see. Well hopefully the OP reads these comments as well and gets a proper system going instead of doing it on the device. – Filled Stacks Feb 28 '19 at 06:05
  • https://docs.revenuecat.com/discuss/5ea749abbbfeff0018f166f3 I am facing this issue. Please help. I can pay you. – Chirag Chopra Nov 09 '20 at 09:29
0

Yeah, as to the vulnerabilities, there is one way to take the issue of users changing their time manually just to deceive the app, the solution I thought of is letting the DateTime come from the server this way, whether users change the Date and Time or not you end up with the correct time frame. I hope this helps.

As to checking whether subscription has expired, just follow the step @awaik gave and in addition, you can make request to your api to store the dateTime when the subscription was purchased and when you expect the subscription to expire. I suggest you save the purchase date and expected date in your server rather than on user device as clearing the cache or data directory of the app will lead to loss of the saved data. Goodluck.

John Oyekanmi
  • 342
  • 1
  • 7