paystack_flutter_sa 1.0.2 paystack_flutter_sa: ^1.0.2 copied to clipboard
A Flutter plugin for making payments via Paystack Payment Gateway. Completely supports Android and iOS.
import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:paystack_flutter_sa/paystack_flutter_sa.dart';
// To get started quickly, change this to your heroku deployment of
// https://github.com/PaystackHQ/sample-charge-card-backend
// Step 1. Visit https://github.com/PaystackHQ/sample-charge-card-backend
// Step 2. Click "Deploy to heroku"
// Step 3. Login with your heroku credentials or create a free heroku account
// Step 4. Provide your secret key and an email with which to start all test transactions
// Step 5. Replace {YOUR_BACKEND_URL} below with the url generated by heroku (format https://some-url.herokuapp.com)
String backendUrl = 'https://clean-ride-api.projectspreview.net/';
// Set this to a public key that matches the secret key you supplied while creating the heroku instance
String paystackPublicKey = 'pk_test_9e72bebafb59f7440b8bf3dbb94cabac4c4bdc8e';
const String appName = 'Paystack Example';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: appName,
darkTheme: ThemeData.dark(),
home: new HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _scaffoldKey = new GlobalKey<ScaffoldState>();
final _formKey = GlobalKey<FormState>();
final _verticalSizeBox = const SizedBox(height: 20.0);
final _horizontalSizeBox = const SizedBox(width: 10.0);
final plugin = PaystackPlugin();
var _border = new Container(
width: double.infinity,
height: 1.0,
color: Colors.red,
);
int _radioValue = 0;
CheckoutMethod _method = CheckoutMethod.selectable;
bool _inProgress = false;
String? _cardNumber;
String? _cvv;
int? _expiryMonth;
int? _expiryYear;
@override
void initState() {
plugin.initialize(publicKey: paystackPublicKey);
super.initState();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(title: const Text(appName)),
body: new Container(
padding: const EdgeInsets.all(20.0),
child: new Form(
key: _formKey,
child: new SingleChildScrollView(
child: new ListBody(
children: <Widget>[
new Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Expanded(
child: const Text('Initalize transaction from:'),
),
new Expanded(
child: new Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
new RadioListTile<int>(
value: 0,
groupValue: _radioValue,
onChanged: _handleRadioValueChanged,
title: const Text('Local'),
),
new RadioListTile<int>(
value: 1,
groupValue: _radioValue,
onChanged: _handleRadioValueChanged,
title: const Text('Server'),
),
]),
)
],
),
_border,
_verticalSizeBox,
new TextFormField(
decoration: const InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'Card number',
),
onSaved: (String? value) => _cardNumber = value,
),
_verticalSizeBox,
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Expanded(
child: new TextFormField(
decoration: const InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'CVV',
),
onSaved: (String? value) => _cvv = value,
),
),
_horizontalSizeBox,
new Expanded(
child: new TextFormField(
decoration: const InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'Expiry Month',
),
onSaved: (String? value) => _expiryMonth = int.tryParse(value ?? ""),
),
),
_horizontalSizeBox,
new Expanded(
child: new TextFormField(
decoration: const InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'Expiry Year',
),
onSaved: (String? value) => _expiryYear = int.tryParse(value ?? ""),
),
)
],
),
_verticalSizeBox,
Theme(
data: Theme.of(context).copyWith(
colorScheme: Theme.of(context).colorScheme.copyWith(secondary: green),
primaryColorLight: Colors.white,
primaryColorDark: navyBlue,
textTheme: Theme.of(context).textTheme.copyWith(
bodyMedium: TextStyle(
color: lightBlue,
),
),
),
child: Builder(
builder: (context) {
return _inProgress
? new Container(
alignment: Alignment.center,
height: 50.0,
child: Platform.isIOS ? new CupertinoActivityIndicator() : new CircularProgressIndicator(),
)
: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_getPlatformButton('Charge Card', () => _startAfreshCharge()),
_verticalSizeBox,
_border,
new SizedBox(
height: 40.0,
),
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Flexible(
flex: 3,
child: new DropdownButtonHideUnderline(
child: new InputDecorator(
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: true,
hintText: 'Checkout method',
),
child: new DropdownButton<CheckoutMethod>(
value: _method,
isDense: true,
onChanged: (CheckoutMethod? value) {
if (value != null) {
setState(() => _method = value);
}
},
items: banks.map((String value) {
return new DropdownMenuItem<CheckoutMethod>(
value: _parseStringToMethod(value),
child: new Text(value),
);
}).toList(),
),
),
),
),
_horizontalSizeBox,
new Flexible(
flex: 2,
child: new Container(
width: double.infinity,
child: _getPlatformButton(
'Checkout',
() => _handleCheckout(context),
),
),
),
],
)
],
);
},
),
)
],
),
),
),
),
);
}
void _handleRadioValueChanged(int? value) {
if (value != null) setState(() => _radioValue = value);
}
_handleCheckout(BuildContext context) async {
if (_method != CheckoutMethod.card && _isLocal) {
_showMessage('Select server initialization method at the top');
return;
}
setState(() => _inProgress = true);
_formKey.currentState?.save();
Charge charge = Charge()
..amount = 10000 // In base currency
..email = 'customer@email.com'
..card = _getCardFromUI();
if (!_isLocal) {
var accessCode = await _fetchAccessCodeFrmServer(_getReference());
charge.accessCode = accessCode;
} else {
charge.reference = _getReference();
}
try {
CheckoutResponse response = await plugin.checkout(
context,
method: _method,
charge: charge,
fullscreen: false,
logo: MyLogo(),
);
print('Response = $response');
setState(() => _inProgress = false);
_updateStatus(response.reference, '$response');
} catch (e) {
setState(() => _inProgress = false);
_showMessage("Check console for error");
rethrow;
}
}
_startAfreshCharge() async {
_formKey.currentState?.save();
Charge charge = Charge();
charge.card = _getCardFromUI();
setState(() => _inProgress = true);
if (_isLocal) {
// Set transaction params directly in app (note that these params
// are only used if an access_code is not set. In debug mode,
// setting them after setting an access code would throw an exception
charge
..amount = 10000 // In base currency
..email = 'customer@email.com'
..reference = _getReference()
..account
..putCustomField('Charged From', 'Flutter SDK');
_chargeCard(charge);
} else {
// Perform transaction/initialize on Paystack server to get an access code
// documentation: https://developers.paystack.co/reference#initialize-a-transaction
charge.accessCode = await _fetchAccessCodeFrmServer(_getReference());
_chargeCard(charge);
}
}
_chargeCard(Charge charge) async {
final response = await plugin.chargeCard(context, charge: charge);
final reference = response.reference;
// Checking if the transaction is successful
if (response.status) {
_verifyOnServer(reference);
return;
}
// The transaction failed. Checking if we should verify the transaction
if (response.verify) {
_verifyOnServer(reference);
} else {
setState(() => _inProgress = false);
_updateStatus(reference, response.message);
}
}
String _getReference() {
String platform;
if (Platform.isIOS) {
platform = 'iOS';
} else {
platform = 'Android';
}
return 'ChargedFrom${platform}_${DateTime.now().millisecondsSinceEpoch}';
}
PaymentCard _getCardFromUI() {
// Using just the must-required parameters.
return PaymentCard(
number: _cardNumber,
cvc: _cvv,
expiryMonth: _expiryMonth,
expiryYear: _expiryYear,
);
// Using Cascade notation (similar to Java's builder pattern)
// return PaymentCard(
// number: cardNumber,
// cvc: cvv,
// expiryMonth: expiryMonth,
// expiryYear: expiryYear)
// ..name = 'Segun Chukwuma Adamu'
// ..country = 'Nigeria'
// ..addressLine1 = 'Ikeja, Lagos'
// ..addressPostalCode = '100001';
// Using optional parameters
// return PaymentCard(
// number: cardNumber,
// cvc: cvv,
// expiryMonth: expiryMonth,
// expiryYear: expiryYear,
// name: 'Ismail Adebola Emeka',
// addressCountry: 'Nigeria',
// addressLine1: '90, Nnebisi Road, Asaba, Deleta State');
}
Widget _getPlatformButton(String string, Function() function) {
// is still in progress
Widget widget;
if (Platform.isIOS) {
widget = new CupertinoButton(
onPressed: function,
padding: const EdgeInsets.symmetric(horizontal: 15.0),
color: CupertinoColors.activeBlue,
child: new Text(
string,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
} else {
widget = new ElevatedButton(
onPressed: function,
child: new Text(
string.toUpperCase(),
style: const TextStyle(fontSize: 17.0),
),
);
}
return widget;
}
Future<String?> _fetchAccessCodeFrmServer(String reference) async {
String url = '$backendUrl/new-access-code';
String? accessCode;
try {
print("Access code url = $url");
http.Response response = await http.get(Uri.parse(url));
accessCode = response.body;
print('Response for access code = $accessCode');
} catch (e) {
setState(() => _inProgress = false);
_updateStatus(
reference,
'There was a problem getting a new access code form'
' the backend: $e');
}
return accessCode;
}
void _verifyOnServer(String? reference) async {
_updateStatus(reference, 'Verifying...');
String url = '$backendUrl/verify/$reference';
try {
http.Response response = await http.get(Uri.parse(url));
var body = response.body;
_updateStatus(reference, body);
} catch (e) {
_updateStatus(
reference,
'There was a problem verifying %s on the backend: '
'$reference $e');
}
setState(() => _inProgress = false);
}
_updateStatus(String? reference, String message) {
_showMessage('Reference: $reference \n\ Response: $message', const Duration(seconds: 7));
}
_showMessage(String message, [Duration duration = const Duration(seconds: 4)]) {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(message),
duration: duration,
action: new SnackBarAction(label: 'CLOSE', onPressed: () => ScaffoldMessenger.of(context).removeCurrentSnackBar()),
));
}
bool get _isLocal => _radioValue == 0;
}
var banks = ['Selectable', 'Bank', 'Card'];
CheckoutMethod _parseStringToMethod(String string) {
CheckoutMethod method = CheckoutMethod.selectable;
switch (string) {
case 'Bank':
method = CheckoutMethod.bank;
break;
case 'Card':
method = CheckoutMethod.card;
break;
}
return method;
}
class MyLogo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black,
),
alignment: Alignment.center,
padding: EdgeInsets.all(10),
child: Text(
"CO",
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
);
}
}
const Color green = const Color(0xFF3db76d);
const Color lightBlue = const Color(0xFF34a5db);
const Color navyBlue = const Color(0xFF031b33);