This guide will walk you through implementing a complete payment flow using the Hubtel Merchant Checkout SDK. By the end, you’ll have a working checkout integration in your Flutter app.
final hubtelConfig = HubtelCheckoutConfiguration( merchantApiKey: "QTN1akQ1SzpiM2IxMjA1NTEwZmI0NjYzYTdiY2ZmZmUyNmQ1YmIzZA==", merchantID: "1122334", callbackUrl: "https://your-domain.com/payment-callback",);
Important: The merchantApiKey must be a Base64-encoded string of your Hubtel merchant credentials. Keep this secure and never commit it to version control. Consider using environment variables or secure storage.
The clientReference should be a unique identifier for each transaction. We’re using the uuid package here, but you can use any method to generate unique IDs.
This will apply your brand color throughout the checkout experience.
5
Navigate to the CheckoutScreen
When the user initiates payment (e.g., taps a “Pay Now” button), navigate to the checkout screen:
void initiateCheckout(BuildContext context) async { final result = await Navigator.push( context, MaterialPageRoute( builder: (context) { return CheckoutScreen( purchaseInfo: purchaseInfo, configuration: hubtelConfig, themeConfig: themeConfig, // Optional ); }, ), ); // Handle the checkout result if (result is CheckoutCompletionStatus) { handleCheckoutCompletion(result); }}
6
Handle the payment result
Process the checkout completion status:
void handleCheckoutCompletion(CheckoutCompletionStatus status) { switch (status.status) { case UnifiedCheckoutPaymentStatus.paymentSuccess: // Payment was successful print('Payment successful!'); print('Transaction ID: ${status.transactionId}'); print('Payment Type: ${status.paymentType}'); print('Payment Channel: ${status.paymentChannel}'); // Update your UI, save the transaction, etc. showSuccessDialog(); break; case UnifiedCheckoutPaymentStatus.paymentFailed: // Payment failed print('Payment failed'); showErrorDialog('Payment failed. Please try again.'); break; case UnifiedCheckoutPaymentStatus.pending: // Payment is pending (e.g., bank transfer) print('Payment pending'); showPendingDialog('Your payment is being processed.'); break; case UnifiedCheckoutPaymentStatus.userCancelledPayment: // User cancelled the payment print('User cancelled payment'); break; case UnifiedCheckoutPaymentStatus.unknown: // Status unknown print('Payment status unknown'); showErrorDialog('Unable to determine payment status.'); break; }}
The CheckoutCompletionStatus object contains:
status - The payment status enum
transactionId - Unique transaction identifier
paymentType - Type of payment used (optional)
paymentChannel - Channel used for payment (optional)
The UnifiedCheckoutPaymentStatus enum has the following values:
Status
Description
paymentSuccess
Payment was completed successfully
paymentFailed
Payment attempt failed
pending
Payment is being processed (common with bank transfers)
userCancelledPayment
User closed checkout without completing payment
unknown
Payment status could not be determined
Always verify payment status on your backend using the callback URL. The client-side status should not be the sole source of truth for critical business logic.