Flutter cannot call the Paystack verify endpoint directly (it requires a secret key). After the WebView checkout, extract the transaction reference from the callback URL and send it to your backend. Your backend calls GET https://api.paystack.co/transaction/verify/:reference and returns the result. Show the payment status in Flutter based on your backend response.
Your Flutter app calls this endpoint after checkout. The backend calls Paystack:
// Node.js/Express backend
app.get('/api/pay/verify', async (req, res) => {
var reference = req.query.reference;
if (!reference) return res.status(400).json({ error: 'Reference required' });
var response = await fetch(
'https://api.paystack.co/transaction/verify/' + encodeURIComponent(reference),
{
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY }
}
);
var data = await response.json();
if (!data.status || data.data.status !== 'success') {
return res.status(400).json({
verified: false,
status: data.data?.status || 'error',
message: data.data?.gateway_response || 'Payment not verified'
});
}
res.json({
verified: true,
amount: data.data.amount / 100,
currency: data.data.currency,
reference: data.data.reference,
customer_email: data.data.customer.email
});
});
// lib/services/payment_service.dart
import 'package:http/http.dart' as http;
import 'dart:convert';
class PaymentResult {
final bool verified;
final double? amount;
final String? currency;
final String? reference;
final String? errorMessage;
const PaymentResult({
required this.verified,
this.amount,
this.currency,
this.reference,
this.errorMessage,
});
}
class PaymentService {
static const String _baseUrl = 'https://your-backend.com';
static Future<PaymentResult> verify(String reference) async {
try {
var response = await http.get(
Uri.parse('$_baseUrl/api/pay/verify?reference=$reference'),
headers: {'Content-Type': 'application/json'},
).timeout(const Duration(seconds: 15));
var data = jsonDecode(response.body);
if (response.statusCode == 200 && data['verified'] == true) {
return PaymentResult(
verified: true,
amount: (data['amount'] as num).toDouble(),
currency: data['currency'],
reference: data['reference'],
);
} else {
return PaymentResult(
verified: false,
errorMessage: data['message'] ?? 'Verification failed',
);
}
} catch (e) {
return PaymentResult(
verified: false,
errorMessage: 'Network error: ${e.toString()}',
);
}
}
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
// lib/payment_result_screen.dart
import 'package:flutter/material.dart';
import 'services/payment_service.dart';
class PaymentResultScreen extends StatefulWidget {
final String reference;
const PaymentResultScreen({super.key, required this.reference});
@override
State<PaymentResultScreen> createState() => _PaymentResultScreenState();
}
class _PaymentResultScreenState extends State<PaymentResultScreen> {
late Future<PaymentResult> _verifyFuture;
@override
void initState() {
super.initState();
_verifyFuture = PaymentService.verify(widget.reference);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<PaymentResult>(
future: _verifyFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Verifying payment...'),
],
),
);
}
if (!snapshot.hasData || !snapshot.data!.verified) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 64, color: Colors.red),
const SizedBox(height: 16),
Text(snapshot.data?.errorMessage ?? 'Payment failed'),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Try Again'),
),
],
),
);
}
var result = snapshot.data!;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.check_circle, size: 64, color: Colors.green),
const SizedBox(height: 16),
const Text('Payment Successful', style: TextStyle(fontSize: 24)),
const SizedBox(height: 8),
Text('${result.currency} ${result.amount?.toStringAsFixed(2)}'),
const SizedBox(height: 8),
Text('Ref: ${result.reference}', style: const TextStyle(color: Colors.grey)),
],
),
);
},
),
);
}
}
This guide is part of the Paystack integration guides by language and framework series.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course