How to get tokenID from Stripe credit card in flutter - flutter

I want to get the tokenID from my credit card. I saw this Article is using StripeSource.addSource but the latest stripe package did not have StripeSource.addSource. So I do like this:
StripePayment.paymentRequestWithCardForm(
CardFormPaymentRequest())
.catchError((e) {
print('ERROR ${e.toString()}');
}).then((PaymentMethod paymentMethod) async {
final CreditCard testCard = CreditCard(
number: paymentMethod.card.number,
expMonth: paymentMethod.card.expMonth,
expYear: paymentMethod.card.expYear
);
StripePayment.createTokenWithCard(testCard).then((token) {
print(token.tokenId);
});
});
But I get the error Unhandled Exception: PlatformException(invalidRequest, Missing required param: card[number]., null)
Anyone can help me solve this problem? Thank you

I do like this:
class _MyHomePageState extends State<MyHomePage> {
static String secret = 'sk_test_xxxxxxxxxxxxx';
TextEditingController cardNumberController = new TextEditingController();
TextEditingController expYearController = new TextEditingController();
TextEditingController expMonthController = new TextEditingController();
#override
initState() {
super.initState();
StripePayment.setOptions(
StripeOptions(publishableKey: "pk_testxxxxxx", merchantId: "Test", androidPayMode: 'test')
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: cardNumberController,
decoration: InputDecoration(labelText: 'Card Number'),
),
TextField(
controller: expMonthController,
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Expired Month'),
),
TextField(
controller: expYearController,
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Expired Year'),
),
RaisedButton(
child: Text('card'),
onPressed: () {
final CreditCard testCard = CreditCard(
number: cardNumberController.text,
expMonth: int.parse(expMonthController.text),
expYear: int.parse(expYearController.text)
);
StripePayment.createTokenWithCard(testCard).then((token) {
print(token.tokenId);
createCharge(token.tokenId);
});
},)
],
),
),
);
}
static Future<Map<String, dynamic>> createCharge(String tokenId) async {
try {
Map<String, dynamic> body = {
'amount': '2000',
'currency': 'usd',
'source': tokenId,
'description': 'My first try'
};
var response = await http.post(
'https://api.stripe.com/v1/charges',
body: body,
headers: { 'Authorization': 'Bearer ${secret}','Content-Type': 'application/x-www-form-urlencoded'}
);
return jsonDecode(response.body);
} catch (err) {
print('err charging user: ${err.toString()}');
}
return null;
}
}

You do not need a token if you have a paymentMethod.
This code works for me:
PaymentMethod paymentMethod = PaymentMethod();
paymentMethod = await StripePayment.paymentRequestWithCardForm(
CardFormPaymentRequest(),
).then((PaymentMethod paymentMethod) {
return paymentMethod;
}).catchError((e) {
print('Errore Card: ${e.toString()}');
});
After this, you can check the paymentMethod and if not null you can go ahead with charging the card

Related

init state for FutureBuilder flutter

i am confused where can i implement the init state condition for future builder. see whats wrong in my code. in flutter documentation is refer initState for Future builder widget.
` #override
void initState() {
super.initState();
futureLoginuser = loginuser();
}`
i am trying to navigate new screen after response data arrives.my complete code is here i am using go_router for navigation.
class LoginForm extends StatefulWidget {
const LoginForm({Key? key}) : super(key: key);
#override
LoginFormState createState() {
return LoginFormState();
}
}
class LoginFormState extends State<LoginForm> {
TextEditingController mobileController = TextEditingController();
TextEditingController passwordController = TextEditingController();
final _mobileKey = GlobalKey<FormState>();
final _passwordKey = GlobalKey<FormState>();
get message => null;
get messagecode => null;
get userinfo => null;
get token => null;
Future<Loginuser> loginuser(String mobile, String password) async {
final response = await http.post(
Uri.parse('https:random.url/api/login'),
body: {'mobile': mobile, 'password': password});
if (response.statusCode == 200) {
return Loginuser.fromJson(jsonDecode(response.body));
}
} else {
throw Exception('Failed to update');
}
#override
Widget build(BuildContext context) {
return Form(
key: _mobileKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
TextFormField(
controller: mobileController,
autofocus: true,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Mobile Number",
),
),
TextFormField(
controller: passwordController,
key: _passwordKey,
keyboardType: TextInputType.visiblePassword,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Password",
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
FutureBuilder<Loginuser>(
future: loginuser(mobileController.text.toString(),
passwordController.text.toString()),
builder: (context, snapshot) {
if (snapshot.hasData) {
print('snapshsot.hasdata');
context.go('/Home');
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
child: const Text('Submit')),
),
]));
}
}
`
You are using the FutureBuilder wrong it is a Widget that builds itself based on the latest snapshot of interaction with a Future. Its usually used to build widgets that need input when a certain future is completed.
In your case use this:
//first make the onPressed function async
child ElevatedButton(
child: Container(),
onPressed: () async {
// then await the future You want to complete and then use `.then()`
//method to implement the code that you want to implement when the future is completed
await loginuser(mobileController.text.toString(),
passwordController.text.toString())
.then((result) {
print('future completed');
context.go('/Home');
// For errors use onError to show or check the errors.
}).onError((error, stackTrace) {
print(error);
});
});
And do validate the form for any error with form key validation method.
the only way to wait for the future to complete and do something is to use the Asynchronous function either directly as I have shown above or by using the try/catch method both will work fine.
Try this
LoginUser? loginUser
#override
void initState() async{
super.initState();
futureLoginuser = await loginuser();
... // 👈 Your navigation here
}`
Try to make your build responsive by checking for loginUser
#override
Widget build(BuildContext context) {
futureLoginuser == null ?
CircularProgressIndicator() : <Rest of your widget>
}
The way you are trying to implement is not correct, here is very basic example to do
class LoginForm extends StatefulWidget {
const LoginForm({Key? key}) : super(key: key);
#override
LoginFormState createState() {
return LoginFormState();
}
}
class LoginFormState extends State<LoginForm> {
TextEditingController mobileController = TextEditingController();
TextEditingController passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
// your other variables
bool isValidating = false;
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
TextFormField(
controller: mobileController,
autofocus: true,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Mobile Number",
),
),
TextFormField(
controller: passwordController,
keyboardType: TextInputType.visiblePassword,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Password",
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: isValidating
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
setState(() {
isValidating = !isValidating;
});
final r = await loginuser(
mobileController.text.toString(),
passwordController.text.toString());
if (r != null) {
// save user state locally, using hive or what alternative you want
context.go('/Home');
} else {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Failed'),
));
}
setState(() {
isValidating = !isValidating;
});
}
},
child: const Text('Submit')),
),
]));
}
Future<Loginuser?> loginuser(String mobile, String password) async {
final response = await http.post(Uri.parse('https:random.url/api/login'),
body: {'mobile': mobile, 'password': password});
if (response.statusCode == 200) {
return Loginuser.fromJson(jsonDecode(response.body));
}
return null;
}
}

Post request in flutter giving error but still pushing the object created

Hi im trying to post some data to my backend and when im pushing the data an error is catched that says failed to create expense based on the Create expense method.
Eventhough it gives me that error that certain expense it is added on the list of expenses.
Also I dont know if im doing it right because i want to input an integer to the amount field but im converting it into a string.
And in the button section im using int.parse() for the amount value.
Maybe is this causing the issue ?
import 'dart:async';
import 'dart:convert';
import 'package:intl/intl.dart';
import 'package:fin_app/models/user.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Expenses> createExpense(String category,int amount,String date,String paymentType) async {
final response = await http.post(
Uri.parse('****************'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'category': category,
'amount': amount.toString(),
'date': date,
'paymentType': paymentType
}),
);
if (response.statusCode == 201) {
// If the server did return a 201 CREATED response,
// then parse the JSON.
return Expenses.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 201 CREATED response,
// then throw an exception.
throw Exception('Failed to create expense.');
}
}
void main() {
runApp(const InputTest());
}
class InputTest extends StatefulWidget {
const InputTest({super.key});
#override
State<InputTest> createState() {
return _InputTestState();
}
}
class _InputTestState extends State<InputTest> {
final TextEditingController _controller = TextEditingController();
final TextEditingController _dateInput = TextEditingController();
final TextEditingController _amountInput = TextEditingController();
final TextEditingController _typeInput = TextEditingController();
Future<Expenses>? _futureExpense;
#override
void initState() {
_dateInput.text = ""; //set the initial value of text field
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Container(
alignment: Alignment.topCenter,
padding: const EdgeInsets.all(8.0),
child: (_futureExpense == null) ? buildColumn() : buildFutureBuilder(),
),
),
);
}
Column buildColumn() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _controller,
decoration: const InputDecoration(hintText: 'Enter Title'),
),
TextField(
controller: _dateInput, //editing controller of this TextField
decoration: InputDecoration(
icon: Icon(Icons.calendar_today), //icon of text field
labelText: "Enter Date" //label text of field
),
readOnly: true, //set it true, so that user will not able to edit text
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context, initialDate: DateTime.now(),
firstDate: DateTime(2000), //DateTime.now() - not to allow to choose before today.
lastDate: DateTime(2101)
);
if(pickedDate != null ){
print(pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000
String formattedDate = DateFormat('yyyy-MM-dd').format(pickedDate);
print(formattedDate); //formatted date output using intl package => 2021-03-16
//you can implement different kind of Date Format here according to your requirement
setState(() {
_dateInput.text = formattedDate; //set output date to TextField value.
});
}else{
print("Date is not selected");
}
},
),
TextField(
controller: _amountInput,
decoration: const InputDecoration(hintText: 'Enter amount'),
),
TextField(
controller: _typeInput,
decoration: const InputDecoration(hintText: 'Enter paymentType'),
),
ElevatedButton(
onPressed: () {
setState(() {
_futureExpense = createExpense(_controller.text,int.parse(_amountInput.text),_dateInput.text,_typeInput.text);
});
},
child: const Text('Create Data'),
),
],
);
}
FutureBuilder<Expenses> buildFutureBuilder() {
return FutureBuilder<Expenses>(
future: _futureExpense,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.category!);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
}
the main reason is that your conversion :
jsonEncode(<String, String>{
'category': category,
'amount': amount.toString(),
'date': date,
'paymentType': paymentType
}),
this is wrong. here you convert the value jsonstring but you server accept a amount a integer or float.
you can try this way.
jsonEncode({
'category': category,
'amount': amount.toString(),
'date': date,
'paymentType': paymentType
}),
I hope you problem is sloved

Singin request with firebase is not working

I'm having a problem trying to do a login validation with firebase. Coincidentally I can create a new firebase account register it and I can enter my application. but when I go back to the home screen, I can't log in with that registered account. What do you think is the mistake I'm making?.
The console returns this to me.
Error image
my screen code
class AuthScreen extends StatefulWidget {
const AuthScreen({Key? key}) : super(key: key);
#override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final _auth = FirebaseAuth.instance;
var _isLoading = false;
void _submitAuthForm(
String email,
String userName,
String password,
bool isLogin,
BuildContext ctx,
) async {
UserCredential authResult;
try {
setState(() {
_isLoading = true;
});
if (isLogin) {
authResult = await _auth.signInWithEmailAndPassword(
email: email, password: password);
} else {
authResult = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
await FirebaseFirestore.instance
.collection('users')
.doc(authResult.user!.uid)
.set({
'username': userName,
'email': email,
});
}
} on PlatformException catch (err) {
var message = 'An error ocurred, Please check your credentials';
if (err.message != null) {
message = err.message!;
}
Scaffold.of(ctx).showSnackBar(SnackBar(
content: Text(message),
backgroundColor: Theme.of(ctx).errorColor,
));
setState(() {
_isLoading = false;
});
} catch (err) {
print(err);
setState(() {
_isLoading = false;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColor,
body: AuthForm(_submitAuthForm, _isLoading),
);
}
}
authentication code
class AuthForm extends StatefulWidget {
AuthForm(this.submitFn, this.isLoading);
final bool isLoading;
final void Function(String email, String userName, String password,
bool isLogin, BuildContext ctx) submitFn;
#override
State<AuthForm> createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
final _formKey = GlobalKey<FormState>();
var _isLogin = true;
var _userEmail = '';
var _userName = '';
var _userPassword = '';
void _trySubmit() {
final isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState!.save();
widget.submitFn(
_userEmail.trim(),
_userPassword.trim(),
_userName.trim(),
_isLogin,
context,
);
}
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
margin: EdgeInsets.all(20),
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
key: ValueKey('email'),
validator: (value) {
if (value!.isEmpty ||
!value.contains('#') ||
!value.contains('.com')) {
return 'Please enter a valid email';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(labelText: 'Email address'),
onSaved: (value) {
_userEmail = value!;
},
),
if (!_isLogin)
TextFormField(
key: ValueKey('username'),
validator: (value) {
if (value!.isEmpty || value.length < 4) {
return 'Please enter at least 4 characters';
}
return null;
},
decoration: InputDecoration(labelText: 'Username'),
onSaved: (value) {
_userName = value!;
},
),
TextFormField(
key: ValueKey('password'),
validator: (value) {
if (value!.isEmpty || value.length < 7) {
return 'Password must be at least 7 characters long';
}
return null;
},
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onSaved: (value) {
_userPassword = value!;
},
),
SizedBox(
height: 15,
),
if (widget.isLoading) CircularProgressIndicator(),
if (!widget.isLoading)
ElevatedButton(
onPressed: _trySubmit,
child: Text(_isLogin ? 'Login' : 'Signup'),
),
if (!widget.isLoading)
TextButton(
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
child: Text(_isLogin
? 'Create new account'
: 'I already have a account'))
],
)),
),
),
));
}
}

How to show http errors on presentation layer on networks http calls

I have custom exception class bellow
class Failure implements Exception {
String cause;
Failure(this.cause);
}
and have bellow class to call login request
class Http {
void login(String userName, String password) async {
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
var data = {
'client_id': '..',
'client_secret': '...',
'grant_type': 'password',
'scope': '...',
'username': userName,
'password': password,
};
var url = Uri.parse('http://x.com/connect/token');
var res = await http.post(url, headers: headers, body: data);
if (res.statusCode != 200) {
throw 'http.post error: statusCode= ${res.statusCode}';
}
}
}
The problem is how can I handle errors properly on the presentation layer
class Login extends StatefulWidget {
const Login({Key? key}) : super(key: key);
static const String routeName = '/login';
#override
State<Login> createState() => _LoginState();
}
class _LoginState extends State<Login> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _userName = TextEditingController();
final TextEditingController _password = TextEditingController();
late bool _obscurePassword;
#override
void initState() {
super.initState();
_obscurePassword = true;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: _userName,
decoration: const InputDecoration(
icon: Icon(Icons.person), label: Text('username')),
validator: (value) {
if (value == null || value.isEmpty) {
return 'not empty';
}
return null;
},
),
const SizedBox(
height: 8,
),
TextFormField(
controller: _password,
obscureText: _obscurePassword,
decoration: InputDecoration(
icon: const Icon(Icons.password),
label: const Text('password'),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'not empty';
}
return null;
},
),
const SizedBox(
height: 12,
),
ElevatedButton(
onPressed: () {
onLoginSubmit(context);
},
child: const Text('login'),
),
],
),
),
),
);
}
//-----> how handle errors and show on UI , I get Error: http.post error: statusCode= 400 on console right now
void onLoginSubmit(BuildContext context) async {
Http http = Http();
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
//save token
try {
http.login(_userName.text, _password.text);
} catch (e, s) {
print(s);
}
}
}
}
I get Error: http.post error: statusCode= 400 in console right now, whats the problem?
and If there is a brief article to handle HTTP request secure and properly please provide it in the comments or answer bellow
Use Snackbar if you have scaffold
try {
http.login(_userName.text, _password.text);
} catch (e, s) {
const snackBar = SnackBar(
content: Text('Error!'),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
Or any other toast package
Use await keyword to process Future requests such as an HTTP post request
First change void login(...) in your HTTP class to Future<void> login(...)
Then use await keyword when you want to call this method for example in your UI
void onLoginSubmit(BuildContext context) async {
Http http = Http();
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
//save token
try {
await http.login(_userName.text, _password.text);
} catch (e, s) {
debugPrint(s);
}
}
}
}
And use debugPrint from dart:foundation library instead of print

How can I get token response with username and password textfields? dart/flutter

I'm kind of new to rest api and flutter.
I wanna login with the username and password and get the token response.
How can I get the username and password from the text field for authorization?
I'm also not sure about the auth code is correct?
imports
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> {
final usernameController = TextEditingController();
final passwordController = TextEditingController();
Future<String> authorization() async {
var request = http.MultipartRequest(
'POST', Uri.parse('url'));
request.fields.addAll({
'grant_type': 'info',
'client_id': 'info',
'username': username,
'password': password,
});
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
Map<String, dynamic> auth =
jsonDecode(await response.stream.bytesToString());
return auth['access_token'];
} else {
return "";
}
}
void _setText() {
setState(() {
username = usernameController.text;
password = passwordController.text;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
backgroundColor: Colors.green,
),
body: Column(
children: [
Padding(
child: TextField(
decoration: InputDecoration(labelText: 'username'),
controller: usernameController,
),
),
Padding(
child: TextField(
decoration: InputDecoration(labelText: 'password'),
controller: passwordController,
),
),
RaisedButton(
onPressed: _setText,
child: Text('Login'),
),
],
),
);
}
}
Since you are using TextEditingControllers, you won't have to use Strings to hold the values of username and password.
change them like this:
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
Then, change your API post to this:
Future<String> authorization() async {
var request = http.MultipartRequest(
'POST', Uri.parse('url'));
request.fields.addAll({
'grant_type': 'info',
'client_id': 'info',
'username': usernameController.text, //notice you have to use .text
'password': passwordController.text, //notice you have to use .text
});
Your onPressed like this:
RaisedButton(
onPressed: () async {
String token = await authorization();
print(token); //This will be your token.
},
child: Text('Login'),),
you don't need this anymore:
//void _setText() {
//setState(() {
//username = usernameController.text;
// password = passwordController.text;
//});
//}
You should change a bit your methods to gives the parameters to you function authorization that will call your API :
// Add parameters
Future<String> authorization(username,password) async {
...
}
void _setText() {
// This will call your API with the form parameters
result = await authorization(usernameController.text, passwordController.text);
// result should have your token (= the response from the API)
}