novac_payment_plugin 1.0.0
novac_payment_plugin: ^1.0.0 copied to clipboard
Official Flutter plugin for Novac Payment Gateway. Accept payments seamlessly in your Flutter apps with support for cards, bank transfers, and mobile money.
example/lib/main.dart
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:novac_payment_plugin/novac_payment_plugin.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isInitialized = false;
bool _isLoading = false;
String? _lastTransactionRef;
String _statusMessage = 'Enter your API key to get started';
final TextEditingController _apiKeyController = TextEditingController();
final TextEditingController _amountController = TextEditingController(
text: '5000',
);
final TextEditingController _emailController = TextEditingController(
text: '[email protected]',
);
// Brand Colors
static const Color primaryGreen = Color(0xFFADBD52);
static const Color darkBg = Color(0xFF1A1A1A);
static const Color cardBg = Color(0xFF2A2A2A);
static const Color surfaceBg = Color(0xFF333333);
// Pre-computed colors with alpha values
static const Color primaryGreen05 = Color(0x0DADBD52); // 5% opacity
static const Color primaryGreen15 = Color(0x26ADBD52); // 15% opacity
static const Color primaryGreen20 = Color(0x33ADBD52); // 20% opacity
static const Color primaryGreen30 = Color(0x4DADBD52); // 30% opacity
static const Color primaryGreen50 = Color(0x80ADBD52); // 50% opacity
static const Color orange15 = Color(0x26FF9800); // 15% opacity
static const Color orange20 = Color(0x33FF9800); // 20% opacity
static const Color orange30 = Color(0x4DFF9800); // 30% opacity
static const Color white10 = Color(0x1AFFFFFF); // 10% opacity
static const Color white30 = Color(0x4DFFFFFF); // 30% opacity
static const Color white50 = Color(0x80FFFFFF); // 50% opacity
static const Color white70 = Color(0xB3FFFFFF); // 70% opacity
// Currency symbol - use NGN for Android, ₦ for iOS
String get _currencySymbol => Platform.isAndroid ? 'NGN ' : '₦';
@override
void dispose() {
_apiKeyController.dispose();
_amountController.dispose();
_emailController.dispose();
super.dispose();
}
Future<void> _initializeSDK() async {
final apiKey = _apiKeyController.text.trim();
if (apiKey.isEmpty) {
setState(() => _statusMessage = 'Please enter your API key');
return;
}
setState(() => _isLoading = true);
try {
await NovacPaymentPlugin.initialize(
apiKey: apiKey,
primaryColor: '#ADBD52',
backgroundColor: '#FFFFFF',
);
setState(() {
_isInitialized = true;
_statusMessage = 'SDK initialized successfully';
});
} catch (e) {
setState(() => _statusMessage = 'Initialization failed: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _startPayment() async {
if (!_isInitialized) {
setState(() => _statusMessage = 'Please initialize SDK first');
return;
}
final amount = double.tryParse(_amountController.text.trim());
if (amount == null || amount <= 0) {
setState(() => _statusMessage = 'Please enter a valid amount');
return;
}
final email = _emailController.text.trim();
if (email.isEmpty) {
setState(() => _statusMessage = 'Please enter customer email');
return;
}
setState(() => _isLoading = true);
final reference = 'TXN-${DateTime.now().millisecondsSinceEpoch}';
_lastTransactionRef = reference;
try {
final result = await NovacPaymentPlugin.launchCheckout(
amount: amount,
currency: 'NGN',
redirectUrl: 'https://yourapp.com/payment-complete',
transactionReference: reference,
customerData: CustomerData(
email: email,
firstName: 'Test',
lastName: 'User',
phoneNumber: '+2348012345678',
),
customizationData: CustomizationData(
logoUrl: 'https://yourcompany.com/logo.png',
paymentDescription: 'Test Payment',
checkoutModalTitle: 'Complete Payment',
),
);
if (result.isSuccess) {
setState(
() =>
_statusMessage =
'Payment successful! ID: ${result.transactionId}',
);
} else if (result.isCancelled) {
setState(() => _statusMessage = 'Payment cancelled by user');
} else {
setState(
() => _statusMessage = 'Payment failed: ${result.errorMessage}',
);
}
} catch (e) {
setState(() => _statusMessage = 'Payment error: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _verifyPayment() async {
if (_lastTransactionRef == null) {
setState(() => _statusMessage = 'No transaction to verify');
return;
}
setState(() => _isLoading = true);
try {
final result = await NovacPaymentPlugin.verifyPayment(
_lastTransactionRef!,
);
setState(() => _statusMessage = 'Payment status: ${result.status}');
} catch (e) {
setState(() => _statusMessage = 'Verification error: $e');
} finally {
setState(() => _isLoading = false);
}
}
String _formatNaira(String amount) {
final value = double.tryParse(amount) ?? 0;
final formatted = value
.toStringAsFixed(0)
.replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},',
);
return '$_currencySymbol$formatted';
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Novac Payment',
debugShowCheckedModeBanner: false,
theme: ThemeData(
textTheme: GoogleFonts.redHatDisplayTextTheme(
ThemeData.dark().textTheme,
),
brightness: Brightness.dark,
scaffoldBackgroundColor: darkBg,
primaryColor: primaryGreen,
colorScheme: const ColorScheme.dark(
primary: primaryGreen,
secondary: primaryGreen,
surface: cardBg,
),
appBarTheme: const AppBarTheme(
backgroundColor: darkBg,
elevation: 0,
centerTitle: true,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: surfaceBg,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: primaryGreen, width: 2),
),
labelStyle: GoogleFonts.redHatDisplay(color: Colors.white70),
hintStyle: GoogleFonts.redHatDisplay(color: Colors.white38),
prefixStyle: GoogleFonts.redHatDisplay(color: Colors.white),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primaryGreen,
foregroundColor: darkBg,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.redHatDisplay(
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
cardTheme: CardTheme(
color: cardBg,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
home: Scaffold(
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 20),
// Logo / Header with SVG
Center(
child: Column(
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: primaryGreen15,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.all(16),
child: SvgPicture.asset(
'assets/novac_icon.svg',
colorFilter: const ColorFilter.mode(
primaryGreen,
BlendMode.srcIn,
),
),
),
const SizedBox(height: 16),
Text(
'Novac Payment',
style: GoogleFonts.redHatDisplay(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: -0.5,
),
),
const SizedBox(height: 4),
Text(
'SDK Test Environment',
style: GoogleFonts.redHatDisplay(
fontSize: 14,
color: white50,
),
),
],
),
),
const SizedBox(height: 32),
// Status Card
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _isInitialized ? primaryGreen15 : orange15,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _isInitialized ? primaryGreen30 : orange30,
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _isInitialized ? primaryGreen20 : orange20,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
_isInitialized
? Icons.check_circle
: Icons.info_outline,
color: _isInitialized ? primaryGreen : Colors.orange,
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
_statusMessage,
style: GoogleFonts.redHatDisplay(
color:
_isInitialized ? primaryGreen : Colors.orange,
fontWeight: FontWeight.w500,
),
),
),
if (_isLoading)
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color:
_isInitialized ? primaryGreen : Colors.orange,
),
),
],
),
),
const SizedBox(height: 24),
// API Key Section
Text(
'API CONFIGURATION',
style: GoogleFonts.redHatDisplay(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white54,
letterSpacing: 1.5,
),
),
const SizedBox(height: 12),
TextField(
controller: _apiKeyController,
style: GoogleFonts.redHatDisplay(color: Colors.white),
decoration: InputDecoration(
labelText: 'API Key',
hintText: 'Paste your Novac API key',
prefixIcon: Icon(
Icons.key_rounded,
color: _isInitialized ? primaryGreen : Colors.white54,
),
suffixIcon:
_isInitialized
? const Icon(
Icons.check_circle,
color: primaryGreen,
)
: null,
),
enabled: !_isInitialized,
),
const SizedBox(height: 16),
// Initialize Button
ElevatedButton(
onPressed:
(_isLoading || _isInitialized) ? null : _initializeSDK,
style: ElevatedButton.styleFrom(
backgroundColor: _isInitialized ? surfaceBg : primaryGreen,
foregroundColor: _isInitialized ? Colors.white54 : darkBg,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
_isInitialized
? Icons.check_circle
: Icons.rocket_launch_rounded,
size: 20,
),
const SizedBox(width: 8),
Text(
_isInitialized ? 'SDK Initialized' : 'Initialize SDK',
),
],
),
),
if (_isInitialized) ...[
const SizedBox(height: 32),
// Divider
Row(
children: [
const Expanded(child: Divider(color: white10)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'PAYMENT',
style: GoogleFonts.redHatDisplay(
fontSize: 12,
fontWeight: FontWeight.w600,
color: white30,
letterSpacing: 2,
),
),
),
const Expanded(child: Divider(color: white10)),
],
),
const SizedBox(height: 24),
// Amount Card
Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: primaryGreen15,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.payments_rounded,
color: primaryGreen,
size: 24,
),
),
const SizedBox(width: 12),
Text(
'Payment Details',
style: GoogleFonts.redHatDisplay(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
const SizedBox(height: 20),
// Amount Input
TextField(
controller: _amountController,
style: GoogleFonts.redHatDisplay(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
decoration: InputDecoration(
labelText: 'Amount',
prefixText: _currencySymbol,
prefixStyle: GoogleFonts.redHatDisplay(
color: primaryGreen,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
keyboardType: TextInputType.number,
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 16),
// Email Input
TextField(
controller: _emailController,
style: GoogleFonts.redHatDisplay(
color: Colors.white,
),
decoration: const InputDecoration(
labelText: 'Customer Email',
hintText: '[email protected]',
prefixIcon: Icon(
Icons.email_rounded,
color: Colors.white54,
),
),
keyboardType: TextInputType.emailAddress,
),
],
),
),
),
const SizedBox(height: 16),
// Pay Button
ElevatedButton(
onPressed: _isLoading ? null : _startPayment,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.lock_rounded, size: 20),
const SizedBox(width: 8),
Text('Pay ${_formatNaira(_amountController.text)}'),
],
),
),
const SizedBox(height: 12),
// Verify Button
OutlinedButton(
onPressed:
(_isLoading || _lastTransactionRef == null)
? null
: _verifyPayment,
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: BorderSide(
color:
_lastTransactionRef != null
? primaryGreen50
: Colors.white24,
),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.verified_rounded,
size: 20,
color:
_lastTransactionRef != null
? primaryGreen
: Colors.white38,
),
const SizedBox(width: 8),
Text(
'Verify Last Payment',
style: GoogleFonts.redHatDisplay(),
),
],
),
),
if (_lastTransactionRef != null) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: surfaceBg,
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
const Icon(
Icons.receipt_long_rounded,
color: white50,
size: 18,
),
const SizedBox(width: 10),
Expanded(
child: Text(
_lastTransactionRef!,
style: GoogleFonts.redHatDisplay(
fontSize: 12,
color: white70,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
],
],
const SizedBox(height: 32),
// Help Card
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [primaryGreen15, primaryGreen05],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: primaryGreen20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: primaryGreen20,
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.lightbulb_rounded,
color: primaryGreen,
size: 18,
),
),
const SizedBox(width: 10),
Text(
'Quick Start Guide',
style: GoogleFonts.redHatDisplay(
fontWeight: FontWeight.w600,
color: Colors.white,
fontSize: 16,
),
),
],
),
const SizedBox(height: 16),
_buildStep('1', 'Get your API key from Novac Dashboard'),
_buildStep('2', 'Paste key and tap Initialize SDK'),
_buildStep('3', 'Enter payment amount'),
_buildStep('4', 'Tap Pay to test checkout'),
],
),
),
const SizedBox(height: 40),
],
),
),
),
),
);
}
Widget _buildStep(String number, String text) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: primaryGreen20,
borderRadius: BorderRadius.circular(6),
),
child: Center(
child: Text(
number,
style: GoogleFonts.redHatDisplay(
color: primaryGreen,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
text,
style: GoogleFonts.redHatDisplay(
color: white70,
fontSize: 14,
height: 1.4,
),
),
),
],
),
);
}
}