'Flutter In App purchase (subscription) automatically refund after three days
I have integrated in flutter in_app_purchase subscription (android ), but it always automatically refund after 3 days
I am using below code for flutter subscription. I can't find the actual issue in the code, please help what I have missed in this code
import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'util/ConsumableStore.dart'; const bool _kAutoConsume = true; const String _kConsumableId = 'consumable'; const List _kProductIds = ['subscription_item', 'purchase_item']; class StoreScreenNew extends StatefulWidget { @override _StoreScreenState createState() => _StoreScreenState(); } class _StoreScreenState extends State { final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance; StreamSubscription> _subscription; List _notFoundIds = []; List _products = []; List _purchases = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String _queryProductError; bool _isConnected = false; String storeName = ""; @override void initState() { checkInternet().then((onValue) { setState(() { _isConnected = onValue; }); }); Stream purchaseUpdated = InAppPurchaseConnection.instance.purchaseUpdatedStream; _subscription = purchaseUpdated.listen((purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (error) { // handle error here. }); initStoreInfo(); super.initState(); } Future checkInternet() async { try { final result = await InternetAddress.lookup('google.com'); if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { return Future.value(true); } else { return Future.value(false); } } on SocketException catch (_) { return Future.value(false); } } Future initStoreInfo() async { if (Platform.isIOS) { storeName = "iTunes"; } else { storeName = "Play Store"; } final bool isAvailable = await _connection.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = []; _purchases = []; _notFoundIds = []; _purchasePending = false; _loading = false; }); return; } ProductDetailsResponse productDetailResponse = await _connection.queryProductDetails(_kProductIds.toSet()); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _purchasePending = false; _loading = false; }); return; } if (productDetailResponse.productDetails.isEmpty) { setState(() { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _purchasePending = false; _loading = false; }); return; } final QueryPurchaseDetailsResponse purchaseResponse = await _connection.queryPastPurchases(); if (purchaseResponse.error != null) { // handle query past purchase error.. } final List verifiedPurchases = []; for (PurchaseDetails purchase in purchaseResponse.pastPurchases) { if (await _verifyPurchase(purchase)) { verifiedPurchases.add(purchase); } } setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = verifiedPurchases; _notFoundIds = productDetailResponse.notFoundIDs; _purchasePending = false; _loading = false; }); } @override void dispose() { _subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { List stack = []; if (_queryProductError == null) { stack.add( ListView( children: [ _buildConnectionCheckTile(), _buildProductList(), addPrivacy(), addLink() ], ), ); } else { stack.add(Center( child: Text(_queryProductError), )); } if (_purchasePending) { stack.add( Stack( children: [ Opacity( opacity: 0.3, child: const ModalBarrier(dismissible: false, color: Colors.grey), ), Center( child: CircularProgressIndicator(), ), ], ), ); } return MaterialApp( home: Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).primaryColor, automaticallyImplyLeading: true, title: Text('PRO', style: Theme.of(context).textTheme.headline5), leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context, false), )), body: _isConnected ? Stack( children: stack, ) : Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( height: 10, ), Container( margin: EdgeInsets.all(20), child: Text( "Check your internet connection and try again.", textAlign: TextAlign.center, style: TextStyle(color: Colors.black45, fontSize: 26), )) ])), ), ); } Card _buildConnectionCheckTile() { if (_loading) { return Card(child: ListTile(title: const Text('Trying to connect...'))); } final Widget storeHeader = ListTile( leading: Icon(_isAvailable ? Icons.check : Icons.block, color: _isAvailable ? Colors.green : ThemeData.light().errorColor), title: Text( 'The store is ' + (_isAvailable ? 'available' : 'unavailable') + '.'), ); final List children = [ !_isAvailable ? storeHeader : Container() ]; if (!_isAvailable) { children.addAll([ Divider(), ListTile( title: Text('Not connected', style: TextStyle(color: ThemeData.light().errorColor)), subtitle: const Text( 'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'), ), ]); } return Card(child: Column(children: children)); } Card _buildProductList() { if (_loading) { return Card( child: (ListTile( leading: CircularProgressIndicator(), title: Text('Fetching products...')))); } if (!_isAvailable) { return Card(); } final ListTile productHeader = ListTile( title: Text( 'Available Options', style: TextStyle(fontSize: 20), ), ); List productList = []; if (_notFoundIds.isNotEmpty) { productList.add(ListTile( title: Text('[${_notFoundIds.join(", ")}] not found', style: TextStyle(color: ThemeData.light().errorColor)), subtitle: Text( 'This app needs special configuration to run. Please see example/README.md for instructions.'))); } Map purchases = Map.fromEntries(_purchases.map((PurchaseDetails purchase) { if (purchase.pendingCompletePurchase) { InAppPurchaseConnection.instance.completePurchase(purchase); } return MapEntry(purchase.productID, purchase); })); productList.addAll(_products.map( (ProductDetails productDetails) { PurchaseDetails previousPurchase = purchases[productDetails.id]; return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Colors.white, boxShadow: [ BoxShadow(color: Colors.grey, spreadRadius: 1), ], ), margin: EdgeInsets.all(5), padding: EdgeInsets.all(10), child: Column( children: [ Text( productDetails.title, textAlign: TextAlign.center, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: previousPurchase != null ? Colors.green : Colors.black), ), SizedBox( height: 10, ), Divider(), SizedBox( height: 10, ), Text( productDetails.description, textAlign: TextAlign.left, ), SizedBox( height: 20, ), Divider(), Container( alignment: Alignment.bottomRight, child: previousPurchase != null ? Container( padding: const EdgeInsets.all(10.0), decoration: new BoxDecoration( shape: BoxShape.circle, color: Colors.green, ), child: Icon( Icons.check, size: 30, color: Colors.white, )) : FlatButton( child: Text( productDetails.price, style: TextStyle(fontSize: 18), ), color: Colors.green[800], textColor: Colors.white, onPressed: () { PurchaseParam purchaseParam = PurchaseParam( productDetails: productDetails, applicationUserName: null, sandboxTesting: false); if (productDetails.id == _kConsumableId) { _connection.buyConsumable( purchaseParam: purchaseParam, autoConsume: _kAutoConsume || Platform.isIOS); } else { _connection.buyNonConsumable( purchaseParam: purchaseParam); } }, )) ], ), ); }, )); return Card( margin: EdgeInsets.all(10), elevation: 0, child: Column( children: [ productHeader, Divider(), ] + productList, )); } void showPendingUI() { setState(() { _purchasePending = true; }); } void deliverProduct(PurchaseDetails purchaseDetails) async { if (purchaseDetails.productID == _kConsumableId) { await ConsumableStore.save(purchaseDetails.purchaseID); App.setPurchasesStatus(true); setState(() { _purchasePending = false; }); } else { setState(() { _purchases.add(purchaseDetails); _purchasePending = false; }); } } void handleError(IAPError error) { setState(() { _purchasePending = false; }); } Future _verifyPurchase(PurchaseDetails purchaseDetails) { return Future.value(true); } void _handleInvalidPurchase(PurchaseDetails purchaseDetails) { } void _listenToPurchaseUpdated(List purchaseDetailsList) { purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async { if (purchaseDetails.status == PurchaseStatus.pending) { showPendingUI(); } else { if (purchaseDetails.status == PurchaseStatus.error) { handleError(purchaseDetails.error); } else if (purchaseDetails.status == PurchaseStatus.purchased) { bool valid = await _verifyPurchase(purchaseDetails); if (valid) { deliverProduct(purchaseDetails); } else { _handleInvalidPurchase(purchaseDetails); return; } } if (Platform.isAndroid) { if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) { await InAppPurchaseConnection.instance .consumePurchase(purchaseDetails); } } if (purchaseDetails.pendingCompletePurchase) { await InAppPurchaseConnection.instance .completePurchase(purchaseDetails); } } }); } }
Solution 1:[1]
Make sure with a breakpoint that you are calling
InAppPurchaseConnection.instance.completePurchase(purchase);
Maybe something is happening before it is being called.
Maybe dispose() is being called in between.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Donki |