"You Already Have This Item" Flutter In App Purchase - flutter

I use in-app purchase in my app. Hassle free purchase. But when I want to buy the same product for the second time, this Play Store shows me "You Already Have This Item". I want my products to be bought again and again. I don't have any idea how I can solve it. Thank you very much for the help. I left my source codes below.
InAppPurchase iap = InAppPurchase.instance;
bool available = true;
List<ProductDetails> products = [];
List<PurchaseDetails> purchases = [];
StreamSubscription subscription;
int credits = 0;
#override
void initState() {
initialize();
super.initState();
}
#override
void dispose() {
subscription.cancel();
super.dispose();
}
void initialize() async {
available = await iap.isAvailable();
if (available) {
await getProducts();
verifyPurchase();
subscription = iap.purchaseStream.listen((data) => setState(() {
print('New Purchase');
purchases.addAll(data);
verifyPurchase();
}));
}
}
Future<void> getProducts() async {
Set<String> ids = Set.from(['buy_300_wecoin']);
ProductDetailsResponse response = await iap.queryProductDetails(ids);
setState(() {
products = response.productDetails;
});
}
PurchaseDetails hasPurchased(String productID) {
return purchases.firstWhere((purchase) => purchase.productID == productID,
orElse: () => null);
}
bool verifyPurchase() {
PurchaseDetails purchase = hasPurchased('buy_300_wecoin');
if (purchase != null && purchase.status == PurchaseStatus.purchased) {
return true;
}
}
void buyProduct(ProductDetails prod) {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: prod);
iap.buyConsumable(purchaseParam: purchaseParam, autoConsume: false);
}

you should use Consumable In-App Purchase
A consumable product is one that a user consumes to receive in-app content, such as in-game currency. When a user consumes the product, your app dispenses the associated content, and the user can
then purchase the item again.
Note that the App Store does not have any APIs for querying consumable products, and Google Play considers consumable products to no longer be owned once they're marked as consumed and fails to return them here. For restoring these across devices you'll need to persist them on your own server and query that as well.
Try in_app_purchase library and read about consumable IAP using this library.

Related

Flutter in_app_purchase: ^3.1.4 issue on release production

hello i am using in_app_purchase: ^3.1.4 official flutter plugin for purchasing item in my app its non_consumable product user can buy it only one time. i have test this app on internal testing.On internal testing it was working fine but issue was its not working production release anyone know what issue here is my code below please check now
final Set<String> _productIds = {'urdubible'};
late List<ProductDetails> _products;
bool _isPurchased = false;
#override
void initState() {
super.initState();
_checkPurchaseStatus();
// initialize the in-app purchase plugin
InAppPurchase .instance.purchaseStream
.listen((List<PurchaseDetails> purchases) {
// handle any purchase updates
_handlePurchaseUpdates(purchases);
});
// load the available products
_loadProducts();
}
Future<void> _loadProducts() async {
// get the product details from the store
ProductDetailsResponse response =
await InAppPurchase.instance.queryProductDetails(_productIds);
// save the product details to state
setState(() {
_products = response.productDetails;
});
}
void _handlePurchaseUpdates(List<PurchaseDetails> purchases) {
for (PurchaseDetails purchase in purchases) {
switch (purchase.status) {
case PurchaseStatus.purchased:
_savePurchaseStatus(true);
// handle the purchase, e.g. update UI, save the purchase to database, etc.
setState(() {
_isPurchased = true;
});
break;
case PurchaseStatus.error:
// handle any purchase errors
break;
case PurchaseStatus.canceled:
// handle cancelled purchases
break;
}
}
}
Future<void> _purchaseProduct() async {
if (_products.isNotEmpty) {
// initiate the purchase flow
final PurchaseParam purchaseParam =
PurchaseParam(productDetails: _products.first);
InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);
}
}
Future<void> _savePurchaseStatus(bool isPurchased) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('isPurchased', isPurchased);
}
Future<void> _checkPurchaseStatus() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? isPurchased = prefs.getBool('isPurchased');
if (isPurchased != null && isPurchased) {
setState(() {
_isPurchased = true;
});
}
}
in my case I am facing issue with
buildTypes {
release {
signingConfig signingConfigs.debug
minifyEnabled false
shrinkResources false
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
After disabling shrink resources it's working for me

Flutter [in_app_purchase] Get all plans inside subscription

I'm using the in_app_purchase package, but I only can get one plan inside the subscriptions
I have 3 subscriptions:
Basic subscription
Premium subscription
Enterprise subscription
And inside each subscription, I want to have 2 plans:
Month plan
Year plan
I always get the plan that has the "backward compatibility"("This will be the baseline returned by the deprecated Google Play Billing Library method querySkuDetailsAsync()") enabled.
Is any way to get all plans, or do I have to have 6 subscriptions with only 1 plan in each one?
Edit:
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
import 'package:motorline_home/widgets/materials/appbar/appbar_title_widget.dart';
import 'package:motorline_home/widgets/materials/pop_button_widget.dart';
import 'package:rxdart/subjects.dart';
class SubscriptionPage extends StatefulWidget {
const SubscriptionPage({
Key? key,
}) : super(key: key);
#override
State<SubscriptionPage> createState() => _SubscriptionPageState();
}
class _SubscriptionPageState extends State<SubscriptionPage> {
// In app subscriptions
InAppPurchase _inAppPurchase = InAppPurchase.instance;
late StreamSubscription<List<PurchaseDetails>> _inAppPurchaseSubscription;
StreamController<List<ProductDetails>> _streamGooglePlaySubscriptions =
BehaviorSubject();
final List<String> _subscriptionsIDs = [
"basic",
"premium",
"enterprise",
];
#override
void initState() {
super.initState();
// In app purchase subscription
_inAppPurchaseSubscription =
_inAppPurchase.purchaseStream.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
print("In app purchase onDone");
_inAppPurchaseSubscription.cancel();
}, onError: (error) {
print("In app purchase error: ${error.toString()}");
// handle error here.
_inAppPurchaseSubscription.cancel();
});
// Initialize in app purchase
_initializeInAppPurchase();
}
#override
void dispose() {
if (Platform.isIOS) {
final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
_inAppPurchase
.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
iosPlatformAddition.setDelegate(null);
}
// Cancel in app purchase listener
_inAppPurchaseSubscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: AppBarTitleWidget(
title: FlutterI18n.translate(context, "subscriptions"),
),
leading: PopButtonWidget(),
),
// Body
body: Container(),
);
}
void _initializeInAppPurchase() async {
print("Initializing in app purchase");
bool available = await _inAppPurchase.isAvailable();
print("In app purchase initialized: $available");
if (available) {
if (Platform.isIOS) {
final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
_inAppPurchase
.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate());
}
// Get subscriptions
List<ProductDetails> subscriptions = await _getSubscriptions(
productIds:
_subscriptionsIDs.toSet(),
);
// Sort by price
subscriptions.sort((a, b) => a.rawPrice.compareTo(b.rawPrice));
// Add subscriptions to stream
_streamGooglePlaySubscriptions.add(subscriptions);
// DEBUG: Print subscriptions
print("In app purchase subscription subscriptions: ${subscriptions}");
for (var subscription in subscriptions) {
print("In app purchase plan: ${subscription.id}: ${subscription.rawPrice}");
print("In app purchase description: ${subscription.description}");
// HOW GET ALL PLANS IN EACH SUBSCRIPTION ID?
}
await InAppPurchase.instance.restorePurchases();
}
}
// In app purchase updates
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
// If purchase is pending
if (purchaseDetails.status == PurchaseStatus.pending) {
print("In app purchase pending...");
// Show pending ui
} else {
if (purchaseDetails.status == PurchaseStatus.canceled) {
print("In app purchase cancelled");
}
// If purchase failed
if (purchaseDetails.status == PurchaseStatus.error) {
print("In app purchase error");
// Show error
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
print("In app purchase restored or purchased");
}
if (purchaseDetails.pendingCompletePurchase) {
debugPrint("In app purchase complete purchased");
debugPrint(
"In app purchase purchase id : ${purchaseDetails.purchaseID}");
debugPrint(
"In app purchase server data : ${purchaseDetails.verificationData.serverVerificationData}");
debugPrint(
"In app purchase local data : ${purchaseDetails.verificationData.localVerificationData}");
// Verify purchase on backend
try {
// VALIDADE PURCHASE IN BACKEND
} catch (error) {
debugPrint("In app purchase error: ${error.toString()}");
}
}
}
});
}
// Get subscription
Future<List<ProductDetails>> _getSubscriptions(
{required Set<String> productIds}) async {
ProductDetailsResponse response =
await _inAppPurchase.queryProductDetails(productIds);
return response.productDetails;
}
}
/// Example implementation of the
/// [`SKPaymentQueueDelegate`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc).
///
/// The payment queue delegate can be implementated to provide information
/// needed to complete transactions.
class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper {
#override
bool shouldContinueTransaction(
SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
return true;
}
#override
bool shouldShowPriceConsent() {
return false;
}
}
What you like to do is not possible. You need to create for each subscription a new plan, you can't say a Premium Subscription does have a yearly and a monthly plan.
This is not possible at the moment since it's not supported by official plugin (and we don't know when it will be):
https://github.com/flutter/flutter/issues/110909
As they mentioned, solution is one plan per subscription with the extra logic necessary in your app (e.g. to detect if subscription with ID PLUS-year is an upgrade of PLUS-month)

Flutter: get past purchases on Android

I'm refactoring in_app_purchases and I'm trying to get the past purchases. According to the documentation:
The InAppPurchaseConnection.queryPastPurchases method has been
removed. Instead, you should use InAppPurchase.restorePurchases. This
method emits each restored purchase on the
InAppPurchase.purchaseStream, the PurchaseDetails object will be
marked with a status of PurchaseStatus.restored
But the example they provide doesn't get the past purchases, it adds the one you buy at that moment.
I moved from this:
final QueryPurchaseDetailsResponse purchaseResponse =
await _connection.queryPastPurchases();
to this:
final Stream<List<PurchaseDetails>> purchaseUpdated = inAppPurchase.purchaseStream;
print(purchaseUpdated.toList());
I tried the above but the list is empty and for sure my user has purchases as I can show here when I try to buy the same version I bought before:
How could get a List from previous purchases?
You have to listen to the purchaseStream like this code in the example:
final Stream<List<PurchaseDetails>> purchaseUpdated =
_inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
All the purchased items will be added to this stream, so you need to add all the results to your list like below:
final List<PurchaseDetails> purchasedList = [];
final Stream<List<PurchaseDetails>> purchaseUpdated =
_inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
purchasedList.addAll(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
Now you can use purchasedList as previous purchases. By the way, all the newly purchased items will be also added to that stream and purchasedList.
UPDATE: After the above steps, you need to call _inAppPurchase.restorePurchases() to get the previous purchases, all the previous purchases will be added to purchasedList by the purchaseStream.

flutter in_app_purchases not available on emulator - getting error android

I am trying to implement in_app_purchase: ^0.3.5+1 for 1st time.
I have an active product setup on the Google Developer Console but I can't seem to get my emulator to hit the store. The connection's isAvailable method always returns false. Stepping through the code the connection instance seems to show the following error message in the private property _purchasedUpdatedStream. Not sure what this means. Seems relevant. Not sure what steps to take from here. Any help is much appreciated.
The getter '_purchaseUpdatedStream' isn't defined for the class 'GooglePlayConnection'.
'GooglePlayConnection' is from 'package:in_app_purchase/src/in_app_purchase/google_play_connection.dart' ('../../flutterSDK/.pub-cache/hosted/pub.dartlang.org/in_app_purchase-0.3.5+1/lib/src/in_app_purchase/google_play_connection.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named '_purchaseUpdatedStream'.
_purchaseUpdatedStream
Store class looks like this:
import 'dart:async';
import 'dart:io';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:quifil/models/status_ml.dart';
class StoreAPI {
InAppPurchaseConnection _iap;
bool available = true;
StreamSubscription<List<PurchaseDetails>> _subscription;
final String myProductID = 'quifil_ad_free';
bool _isPurchased = false;
List _purchases = [];
List _products = [];
StoreAPI() {
InAppPurchaseConnection.enablePendingPurchases();
_iap = InAppPurchaseConnection.instance;
}
bool get isPurchased {
return _isPurchased;
}
set isPurchased(bool value) {
_isPurchased = value;
}
List get purchases {
return _purchases;
}
set purchases(List value) {
_purchases = value;
}
List get products {
return _products;
}
set products(List value) {
_products = value;
}
Future<Status> fetchStoreInfo() async {
Status status = Status(true);
bool available = await _iap.isAvailable();
if (available) {
await _getProducts();
await _getPastPurchases();
verifyPurchase();
_subscription = _iap.purchaseUpdatedStream.listen((purchases) {
purchases.addAll(purchases);
verifyPurchase();
});
} else {
status.success = false;
status.error = "Store is unavailable";
}
return status;
}
Future<void> _getProducts() async {
Set<String> ids = Set.from([myProductID]);
ProductDetailsResponse response = await _iap.queryProductDetails(ids);
products = response.productDetails;
}
Future<void> _getPastPurchases() async {
QueryPurchaseDetailsResponse response = await _iap.queryPastPurchases();
for (PurchaseDetails purchase in response.pastPurchases) {
if (Platform.isIOS) {
_iap.consumePurchase(purchase);
}
}
purchases = response.pastPurchases;
}
void verifyPurchase() {
PurchaseDetails purchase = hasPurchased(myProductID);
if (purchase != null && purchase.status == PurchaseStatus.purchased) {
if (purchase.pendingCompletePurchase) {
_iap.completePurchase(purchase);
isPurchased = true;
}
}
}
PurchaseDetails hasPurchased(String productID) {
return purchases.firstWhere((purchase) => purchase.productID == productID,
orElse: () => null);
}
}
Turns out I had 2 items I changed to fix this.
My laptop was using wifi but wifi was not the 1st choice when it came to network access. So I disabled my other other LAN adapters. There is also a way to set the priority of your adaptors so that the wifi is top priority. It seems the emulator likes to pick the top priority.
My laptop wifi adapter DNS was using an address I did not recognize. So I changed it to use Google's DNS as 8.8.8.8 and the alternate as 8.8.4.4.

Flutter in_app_purchase 0.2.1 purchaseUpdatedStream.listen not working on Android

I am able to buy a subscription on Android, and the subscription shows up in queryPastPurchases(), but the _listenToPurchaseUpdated() method is never triggered after buying the product. I know that this was a bug in previous releases of flutter in_app_purchase, but it was said to be fixed in 0.2.1. However it doesn't seem to work for me... Is there something wrong with my code?
/// The In App Purchase plugin
InAppPurchaseConnection _iap = InAppPurchaseConnection.instance
/// Updates to purchases
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
initState() {
final Stream purchaseUpdates = InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdates.listen((purchases) {
_purchases.addAll(purchases);
_listenToPurchaseUpdated(_purchases);
});
super.initState();
}
/// Get all products available for sale
Future<void> _getProducts() async {
Set<String> ids = Set.from(['subscription_product']);
ProductDetailsResponse response = await _iap.queryProductDetails(ids);
setState(() {
_products = response.productDetails;
});
if(response.error != null && response.error.message != null){
setState(() {
_iapStoreProblem = response.error.message;
});
}
}
void _buyProduct(ProductDetails prod) {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: prod);
_iap.buyNonConsumable(purchaseParam: purchaseParam);
}
I found the solution.
Turns out I had to use
StreamSubscription _subscription;
instead of
StreamSubscription<List<PurchaseDetails>> _subscription;
and then:
_subscription = _iap.purchaseUpdatedStream.listen((data) => setState(() {
_purchases.addAll(data);
_listenToPurchaseUpdated(data);
}));
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
...
});
}
Also, a small note: PurchaseDetails.status on Android is Null.