telebirr_inapp_sdk 0.1.0
telebirr_inapp_sdk: ^0.1.0 copied to clipboard
A Flutter plugin for integrating Telebirr payment SDK in Flutter applications.
example/lib/main.dart
// ignore_for_file: prefer_typing_uninitialized_variables
import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:telebirr_inapp_sdk/telebirr_inapp_sdk.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Platform.isIOS ? 'Telebirr SDK iOS Demo' : 'Telebirr SDK Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const PaymentScreen(),
);
}
}
class PaymentScreen extends StatefulWidget {
const PaymentScreen({super.key});
@override
State<PaymentScreen> createState() => _PaymentScreenState();
}
class _PaymentScreenState extends State<PaymentScreen> {
final TextEditingController _amountController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isProcessing = false;
String _errorMessage = '';
String receiveCode = "";
// Configuration constants
static const String appId = "YOUR_APP_ID";
static const String shortCode = "YOUR_SHORT_CODE";
/// Disposes the resources used by this state, including the
/// amount text controller, and calls the superclass dispose
/// method. This should be called when the state is removed
/// from the widget tree to free up resources.
@override
void dispose() {
_amountController.dispose();
super.dispose();
}
// Validate amount
String? _validateAmount(String? value) {
if (value == null || value.isEmpty) {
return 'Amount is required';
}
try {
double amount = double.parse(value);
if (amount <= 0) {
return 'Amount must be greater than 0';
}
if (amount > 100000) {
// Example maximum amount
return 'Amount cannot exceed 100,000';
}
} catch (e) {
return 'Please enter a valid number';
}
// Check for decimal places
if (value.contains('.')) {
int decimalPlaces = value.split('.')[1].length;
if (decimalPlaces > 2) {
return 'Maximum 2 decimal places allowed';
}
}
return null;
}
Future<void> _startPayment() async {
if (!_formKey.currentState!.validate()) {
return;
}
try {
double amount = double.parse(_amountController.text);
if (amount <= 0) {
setState(() {
_errorMessage = 'Amount must be greater than 0';
});
return;
}
setState(() {
_isProcessing = true;
_errorMessage = '';
});
// First get the receiveCode from your API
final receiveCodeResult = await _getReceiveCode(
amount: _amountController.text,
title: "Telebirr Payment",
);
if (receiveCodeResult != null &&
receiveCodeResult['createOrderResult']['result']
.toString()
.toLowerCase() ==
'success') {
setState(() {
receiveCode = receiveCodeResult['createOrderResult']['biz_content']
['receiveCode'];
});
// Initialize the SDK and start payment
final sdk = TelebirrInappSdk(
appId: appId,
shortCode: shortCode,
receiveCode: receiveCode,
);
final result = await sdk.startPayment();
debugPrint("Payment result: $result");
// Always set processing to false when we get a response
setState(() {
_isProcessing = false;
});
if (!mounted) return;
// Show appropriate message based on status
bool isSuccess = (result.isNotEmpty && result["status"] == true);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
isSuccess
? "Payment completed successfully!"
: "Payment failed due to: ${result['message']}",
style: TextStyle(color: Colors.white)),
backgroundColor: isSuccess ? Colors.green : Colors.red,
));
} else {
setState(() {
_isProcessing = false;
_errorMessage =
receiveCodeResult?['message'] ?? 'Failed to get receive code';
});
}
} catch (e) {
setState(() {
_isProcessing = false;
_errorMessage = "An error occurred: ${e.toString()}";
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
Platform.isIOS ? 'Telebirr SDK iOS Demo' : 'Telebirr SDK Demo'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.asset(
"images/telebirr.png",
height: 200,
width: 200,
),
const SizedBox(height: 30),
TextFormField(
controller: _amountController,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
labelText: 'Amount (ETB)',
hintText: 'Enter amount',
prefixIcon: const Icon(Icons.monetization_on),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
errorMaxLines: 2,
),
validator: _validateAmount,
enabled: !_isProcessing,
// Format input to allow only numbers and one decimal point
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}')),
],
),
const SizedBox(height: 8),
if (_errorMessage.isNotEmpty)
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Text(
_errorMessage,
style: TextStyle(color: Colors.red.shade700),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _startPayment,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isProcessing
? const SpinKitThreeBounce(
color: Colors.white,
size: 24,
)
: const Text(
'Pay with Telebirr',
style: TextStyle(fontSize: 16),
),
),
],
),
),
),
);
}
// Example implementation of getting receive code from your API
Future<dynamic> _getReceiveCode(
{required String amount, required String title}) async {
setState(() {
_isProcessing = true;
});
var url = "YOUR_BACKEND_API_URL";
Map data = {
"amount": amount,
"title": title,
};
var body = json.encode(data);
try {
http.Response response = await http
.post(
Uri.parse(url),
headers: <String, String>{
"Content-Type": "application/json",
"Accept": "application/json"
},
body: body,
)
.timeout(
const Duration(seconds: 15),
onTimeout: () {
setState(() {
_isProcessing = false;
});
throw TimeoutException("The connection has timed out!");
},
);
setState(() {
_isProcessing = false;
});
return json.decode(response.body);
} catch (e) {
setState(() {
_isProcessing = false;
});
return null;
}
}
}