I am trying to update my existing application on google play store from Android Java with Flutter Dart. On my existing application, I have an in-app purchase that uses google pay which works perfectly (With android java coding) but I am looking for how to implement google pay in-app purchase with flutter dart.
I found out GooglePay is not a payment gateway but rather a ladder to gateway, so you have to integrate a payment gateway system with google and from the list i recommend STRIPE. which i will be explaining below.
I came across two. options to achieve this.
Google Pay plugin - This make use of STRIPE Payment Gateway Automatically.
Flutter Google Pay - This has multiple, custom payment gateway you can make use of.
REQUIREMENT
Ensure you have your stripe account setup - If you don't already visit Stripe Payment Gateway to setup your account
The first plugin Google Pay plugin
Ensure you initialize the plugin with your stripe payment secret key
GooglePay.initializeGooglePay("pk_test_H5CJvRiPfCrRS44bZJLu46fM00UjQ0vtRN");
This plugin has code structure as this.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:google_pay/google_pay.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
String _googlePayToken = 'Unknown';
#override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await GooglePay.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
await GooglePay.initializeGooglePay("pk_test_H5CJvRiPfCrRS44bZJLu46fM00UjQ0vtRN");
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
children: <Widget>[
Text('Running on: $_platformVersion\n'),
Text('Google pay token: $_googlePayToken\n'),
FlatButton(
child: Text("Google Pay Button"),
onPressed: onButtonPressed,
)
]
),
),
),
);
}
void onButtonPressed() async{
setState((){_googlePayToken = "Fetching";});
try {
await GooglePay.openGooglePaySetup(
price: "5.0",
onGooglePaySuccess: onSuccess,
onGooglePayFailure: onFailure,
onGooglePayCanceled: onCancelled);
setState((){_googlePayToken = "Done Fetching";});
} on PlatformException catch (ex) {
setState((){_googlePayToken = "Failed Fetching";});
}
}
void onSuccess(String token){
setState((){_googlePayToken = token;});
}
void onFailure(){
setState((){_googlePayToken = "Failure";});
}
void onCancelled(){
setState((){_googlePayToken = "Cancelled";});
}
}
The second plugin Flutter Google Pay which make use of other payment gateway
you can see the list here Payment Gateways
This plugin has two methods for you initialization - either using stripe or other gateways
FOR STRIPE USE THIS METHOD AND INITIALIZE WITH YOUR STRIPE SECRET KEY
_makeStripePayment() async {
var environment = 'rest'; // or 'production'
if (!(await FlutterGooglePay.isAvailable(environment))) {
_showToast(scaffoldContext, 'Google pay not available');
} else {
PaymentItem pm = PaymentItem(
stripeToken: 'pk_test_1IV5H8NyhgGYOeK6vYV3Qw8f',// stripe public key
stripeVersion: "2018-11-08",
currencyCode: "usd",
amount: "0.10",
gateway: 'stripe');
FlutterGooglePay.makePayment(pm).then((Result result) {
if (result.status == ResultStatus.SUCCESS) {
_showToast(scaffoldContext, 'Success');
}
}).catchError((dynamic error) {
_showToast(scaffoldContext, error.toString());
});
}
}
FOR OTHER PAYMENT GATEWAYS
_makeCustomPayment() async {
var environment = 'rest'; // or 'production'
if (!(await FlutterGooglePay.isAvailable(environment))) {
_showToast(scaffoldContext, 'Google pay not available');
} else {
///docs https://developers.google.com/pay/api/android/guides/tutorial
PaymentBuilder pb = PaymentBuilder()
..addGateway("example")
..addTransactionInfo("1.0", "USD")
..addAllowedCardAuthMethods(["PAN_ONLY", "CRYPTOGRAM_3DS"])
..addAllowedCardNetworks(
["AMEX", "DISCOVER", "JCB", "MASTERCARD", "VISA"])
..addBillingAddressRequired(true)
..addPhoneNumberRequired(true)
..addShippingAddressRequired(true)
..addShippingSupportedCountries(["US", "GB"])
..addMerchantInfo("Example");
FlutterGooglePay.makeCustomPayment(pb.build()).then((Result result) {
if (result.status == ResultStatus.SUCCESS) {
_showToast(scaffoldContext, 'Success');
} else if (result.error != null) {
_showToast(context, result.error);
}
}).catchError((error) {
//TODO
});
}
}
You should have no problem to use g pay with in-app. There is no need to write specific code for that. You just need to make sure you are setting up your merchant in the google billing platform. Check out this tutorial https://medium.com/flutter-community/in-app-purchases-with-flutter-a-comprehensive-step-by-step-tutorial-b96065d79a21
Related
I'm trying to implement purchases in my app with the official in_app_purchase plugin but the purchaseStream.listen method is not working or firing any events. The only way to get it to work is to call _inAppPurchase.restorePurchases() which is not what I want as the products are now returned with a restored (not purchased) status.
Can anyone advise on how to get this event firing or point out any mistakes in my code? My code is almost exactly the same as the codelab and I have tested with the plugin version used in the codelab ^3.0.4 as well as the latest version ^3.0.6
class PurchasesModel with ChangeNotifier {
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
late StreamSubscription<List<PurchaseDetails>> _subscription;
List _purchases = [];
List<ProductDetails> _products = [];
List get purchases => _purchases;
set purchases(List value) {
_purchases = value;
notifyListeners();
}
List<ProductDetails> get products => _products;
set products(List<ProductDetails> value) {
_products = value;
notifyListeners();
}
//Constructor
PurchasesModel() {
final purchaseUpdated = _inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate,
onDone: _updateStreamOnDone,
onError: _updateStreamOnError,
);
loadPurchases();
}
Future<void> _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) async {
for (var purchaseDetails in purchaseDetailsList) {
await _handlePurchase(purchaseDetails);
}
notifyListeners();
}
Future<void> _handlePurchase(PurchaseDetails purchaseDetails) async {
....
}
void _updateStreamOnDone() {
_subscription.cancel();
}
void _updateStreamOnError(dynamic error) {
// ignore: avoid_print
print(error);
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
}
And the Widget build method in main.dart
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<PurchasesModel>(
create: (_) => PurchasesModel(),
lazy: false,
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark().copyWith(
primaryColor: primaryColor,
scaffoldBackgroundColor: backgroundColor,
),
home: App(),
),
);
}
This event is triggered only when there is a response for purchase or restore. So first you get the list of products available for sale. That's the
// Set literals require Dart 2.2. Alternatively, use
// `Set<String> _kIds = <String>['product1', 'product2'].toSet()`.
const Set<String> _kIds = <String>{'product1', 'product2'};
final ProductDetailsResponse response =
await InAppPurchase.instance.queryProductDetails(_kIds);
if (response.notFoundIDs.isNotEmpty) {
// Handle the error.
}
List<ProductDetails> products = response.productDetails;
Here you will get the list of products for sale .
Then when there is a purchase event we use
final ProductDetails productDetails = ... // Saved earlier from queryProductDetails().
final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails);
if (_isConsumable(productDetails)) {
InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam);
} else {
InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);
}
// From here the purchase flow will be handled by the underlying store.
// Updates will be delivered to the `InAppPurchase.instance.purchaseStream`.
Here we pass one product detail to buy consumable or buy non consumable and this is listened in the method you mentioned
Edit
To restore you just call this methos and it will be listened in the listener
await InAppPurchase.instance.restorePurchases();
I am using "mobile_number(version - 1.0.3)" plugin to get mobile number in flutter app, am running in original device but i couldn't get mobile number.instead of errors i can get mobile number as null along with other sim details as shown in screen shot.
help me to resolve this problem, i had just copy pasted the example given by plugin that is the code
plugin link
It says:
Note: If the mobile number is not pre-exist on sim card it will not return te phone number.
I think mobile number does not pre-exist on the SIM if the SIM is not original (i.e. replaced)
If the phone number isn't stored on the sim (aka null), then you can't get it from anywhere else, in that case you probably want to forward the user to a different page where they can type the phone number using TextField and then store it somewhere
Use Mobile_number package to get mobile number and other details. For example
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobile_number/mobile_number.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _mobileNumber = '';
List<SimCard> _simCard = <SimCard>[];
#override
void initState() {
super.initState();
MobileNumber.listenPhonePermission((isPermissionGranted) {
if (isPermissionGranted) {
initMobileNumberState();
} else {}
});
initMobileNumberState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initMobileNumberState() async {
if (!await MobileNumber.hasPhonePermission) {
await MobileNumber.requestPhonePermission;
return;
}
String mobileNumber = '';
// Platform messages may fail, so we use a try/catch PlatformException.
try {
mobileNumber = (await MobileNumber.mobileNumber)!;
_simCard = (await MobileNumber.getSimCards)!;
} on PlatformException catch (e) {
debugPrint("Failed to get mobile number because of '${e.message}'");
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_mobileNumber = mobileNumber;
});
}
Widget fillCards() {
List<Widget> widgets = _simCard
.map((SimCard sim) => Text(
'Sim Card Number: (${sim.countryPhonePrefix}) - ${sim.number}\nCarrier Name: ${sim.carrierName}\nCountry Iso: ${sim.countryIso}\nDisplay Name: ${sim.displayName}\nSim Slot Index: ${sim.slotIndex}\n\n'))
.toList();
return Column(children: widgets);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
children: <Widget>[
Text('Running on: $_mobileNumber\n'),
fillCards()
],
),
),
),
);
}
}
I want the users of my app to always have the latest version. If they don't have the latest version, it should download the latest version from play store automatically on app startup. I'm using in_app_update for that. I'm performing Performs immediate update
Below is the code of splash screen which came after main. Here I check for update in route function, if update is available then perform update and navigate to homeView, If not simply navigate to homeView
But app never informed new user about update whenever new version is uploaded on playstore. They have to manually go to playstore to update an app. Why is that? Am I doing something wrong in a code or do I need to do something extra?
import 'package:in_app_update/in_app_update.dart';
class SplashView extends StatefulWidget {
#override
_SplashViewState createState() => _SplashViewState();
}
class _SplashViewState extends State<SplashView> {
AppUpdateInfo _updateInfo; // --- To check for update
#override
void initState() {
super.initState();
startTimer();
}
startTimer() async {
var duration = Duration(milliseconds: 1500);
return Timer(duration, route);
}
route() async {
await checkForUpdate();
bool visiting = await ConstantsFtns().getVisitingFlag();
if (_updateInfo?.updateAvailable == true) {
InAppUpdate.performImmediateUpdate().catchError((e) => _showError(e));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeView(),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeView(),
),
);
}
}
Future<void> checkForUpdate() async {
InAppUpdate.checkForUpdate().then((info) {
setState(() {
_updateInfo = info;
});
}).catchError((e) => _showError(e));
}
void _showError(dynamic exception) {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content:
Text("Exception while checking for error: " + exception.toString()),
));
#override
Widget build(BuildContext context) {
return Material(
child: AnimatedSplashScreen(
.............................
),
);
}
}
I don't know why suggested solution of similar question is not working for me.
https://stackoverflow.com/a/62129373/7290043
By updating app itself without asking user is policy violation that may lead you to suspension of app. read this before trying anything like this: Device and Network Abuse
You can ask users to update app whenever new update is available.
Edit:
code for Finding latest version on playstore:
getAndroidStoreVersion(String id) async {
final url = 'https://play.google.com/store/apps/details?id=$id';
final response = await http.get(url);
if (response.statusCode != 200) {
print('Can\'t find an app in the Play Store with the id: $id');
return null;
}
final document = parse(response.body);
final elements = document.getElementsByClassName('hAyfc');
final versionElement = elements.firstWhere(
(elm) => elm.querySelector('.BgcNfc').text == 'Current Version',
);
dynamic storeVersion = versionElement.querySelector('.htlgb').text;
return storeVersion;
}
For mobile version:
i have used this package Package_info
To check if latest version is available or not:
getUpdateInfo(newVersion) async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String playStoreVersion =
await getAndroidStoreVersion(packageInfo.packageName);
int playVersion = int.parse(playStoreVersion.trim().replaceAll(".", ""));
int CurrentVersion=
int.parse(packageInfo.version.trim().replaceAll(".", ""));
if (playVersion > CurrentVersion) {
_showVersionDialog(context, packageInfo.packageName);
}
}
You can design your pop up as per your convenience.
So am trying to use google pay in my flutter application and below is my code. When I run the application is shows google pay dialog with my google pay email and card details and an error "Unrecognised app. Please make sure that you trust this app before proceeding. When I press the "Continue" button I get "Request failed" "Unexpected developer error, please try again later" Please what am I doing wrong?
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:google_pay/google_pay.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
String _googlePayToken = 'Unknown';
#override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await GooglePay.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
await GooglePay.initializeGooglePay("my google play base64EncodedPublicKey");
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
children: <Widget>[
Text('Running on: $_platformVersion\n'),
Text('Google pay token: $_googlePayToken\n'),
FlatButton(
child: Text("Google Pay Button"),
onPressed: onButtonPressed,
)
]
),
),
),
);
}
void onButtonPressed() async{
setState((){_googlePayToken = "Fetching";});
try {
await GooglePay.openGooglePaySetup(
price: "10.0",
onGooglePaySuccess: onSuccess,
onGooglePayFailure: onFailure,
onGooglePayCanceled: onCancelled);
setState((){_googlePayToken = "Done Fetching";});
} on PlatformException catch (ex) {
setState((){_googlePayToken = "Failed Fetching";});
}
}
void onSuccess(String token){
setState((){_googlePayToken = token;});
}
void onFailure(){
setState((){_googlePayToken = "Failure";});
}
void onCancelled(){
setState((){_googlePayToken = "Cancelled";});
}
} ```
When I run the application is shows google pay dialog with my google pay email and card details and an error "Unrecognised app. Please make sure that you trust this app before proceeding. When I press the "Continue" button I get "Request failed" "Unexpected developer error, please try again later" Please what am I doing wrong?
I need help regarding the images in my app. I would like to add 3 buttons for:
Download
Save as
Change phone wallpaper
I'm not using urls. I already have my images in an assets repository.
Do you have any idea how I can do that? Thank you.
You can copy paste run full code below
You can use package https://pub.dev/packages/wallpaper_manager
You can directly set wallpaper with image in assets
Example code's assets image path is "assets/tmp1.jpg"
code snippet
result = await WallpaperManager.setWallpaperFromAsset(
assetPath, WallpaperManager.HOME_SCREEN);
working demo
full code
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:wallpaper_manager/wallpaper_manager.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
String _wallpaperFile = 'Unknown';
String _wallpaperAsset = 'Unknown';
#override
void initState() {
super.initState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await WallpaperManager.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> setWallpaperFromFile() async {
setState(() {
_wallpaperFile = "Loading";
});
String result;
var file = await DefaultCacheManager().getSingleFile(
'https://images.unsplash.com/photo-1542435503-956c469947f6');
// Platform messages may fail, so we use a try/catch PlatformException.
try {
result = await WallpaperManager.setWallpaperFromFile(
file.path, WallpaperManager.HOME_SCREEN);
} on PlatformException {
result = 'Failed to get wallpaper.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_wallpaperFile = result;
});
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> setWallpaperFromAsset() async {
setState(() {
_wallpaperAsset = "Loading";
});
String result;
String assetPath = "assets/tmp1.jpg";
// Platform messages may fail, so we use a try/catch PlatformException.
try {
result = await WallpaperManager.setWallpaperFromAsset(
assetPath, WallpaperManager.HOME_SCREEN);
} on PlatformException {
result = 'Failed to get wallpaper.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_wallpaperAsset = result;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Column(
children: <Widget>[
RaisedButton(
child: Text("Platform Version"),
onPressed: initPlatformState,
),
Center(
child: Text('Running on: $_platformVersion\n'),
),
RaisedButton(
child: Text("Set wallpaper from file"),
onPressed: setWallpaperFromFile,
),
Center(
child: Text('Wallpaper status: $_wallpaperFile\n'),
),
RaisedButton(
child: Text("Set wallpaper from asset"),
onPressed: setWallpaperFromAsset,
),
Center(
child: Text('Wallpaper status: $_wallpaperAsset\n'),
),
],
)),
);
}
}