PayFast Flutter SDK

πŸš€ Pakistan’s First PayFast Flutter SDK

Pakistan’s first Flutter SDK designed to simplify PayFast payment gateway integration for modern Flutter apps on Android, iOS, and Web.

🎯 Features

  • Production-Ready: Fully tested and production-ready SDK
  • Clean API: Simple, Stripe-like interface for easy integration
  • Secure Token Authentication: SHA256 signature generation for secure transactions
  • Cross-Platform Checkout: WebView on Android/iOS and HTML form POST checkout on Flutter Web
  • Customizable Configuration: Full control over payment parameters
  • Error Handling: Comprehensive error handling and user feedback
  • Support for Multiple Currencies: Flexible currency support (default: PKR)
  • Redirect Monitoring: Success/failure URL monitoring with callback support
  • Nullable Safety: Fully null-safe Dart code

πŸ“‹ Requirements

  • Flutter SDK: >=3.10.0
  • Dart SDK: >=3.0.0 <4.0.0
  • Active PayFast merchant account

πŸ“¦ Installation

Add this to your pubspec.yaml:

dependencies:
  payfast_flutter:
    path: ../  # For local development

Then run:

flutter pub get

πŸš€ Quick Start

Basic Implementation (3 steps)

import 'package:flutter/material.dart';
import 'package:payfast_flutter/payfast_flutter.dart';

class CheckoutPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Checkout')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => _startPayment(context),
          child: Text('Pay 100 PKR'),
        ),
      ),
    );
  }

  void _startPayment(BuildContext context) {
    final config = PayFastConfig(
      merchantId: "XXXX",   
      securedKey: "XXXX",
      basketId: "ORDER123",
      amount: "100",
      successUrl: "https://example.com/success",
      failureUrl: "https://example.com/failure",
      checkoutUrl: "https://example.com/ipn",
    );

    PayFast.startPayment(
      context: context,
      config: config,
      onResult: (result) {
        if (result.success) {
          print('βœ“ Payment successful!');
          print('Transaction ID: ${result.transactionId}');
        } else {
          print('βœ— Payment failed: ${result.message}');
        }
      },
    );
  }
}

πŸ“š API Reference

PayFastConfig

Configuration class for payment parameters.

Required Parameters

PayFastConfig(
  merchantId: "241665",           // Your PayFast merchant ID
  securedKey: "XXXX",             // Your secure key
  basketId: "ORDER123",           // Unique order ID
  amount: "100.00",               // Amount in decimal format
  successUrl: "https://...",      // Redirect on success
  failureUrl: "https://...",      // Redirect on failure
  checkoutUrl: "https://...",     // IPN endpoint
)

Optional Parameters

PayFastConfig(
  // ... required params ...
  currency: "PKR",                // Default: "PKR"
  txnDesc: "Product Purchase",    // Default: "Payment Transaction"
  customerEmail: "user@example.com",
  customerMobile: "03001234567",
  environment: "sandbox",         // Default: "sandbox"
  additionalDescription: "Order details", // Default: "Flutter Payment"
)

Available Properties

Property Type Default Description
merchantId String Required PayFast merchant ID
securedKey String Required Secure key for authentication
basketId String Required Unique transaction ID
amount String Required Payment amount (decimal)
successUrl String Required Success redirect URL
failureUrl String Required Failure redirect URL
checkoutUrl String Required IPN checkout URL
currency String "PKR" Currency code
txnDesc String "Payment Transaction" Transaction description
customerEmail String? null Customer email (optional)
customerMobile String? null Customer phone (optional)
environment String "sandbox" "sandbox" or "live"
additionalDescription String "Flutter Payment" Additional description

PayFast

Main SDK class for initiating payments.

startPayment()

Initiates a complete payment flow.

PayFast.startPayment({
  required BuildContext context,
  required PayFastConfig config,
  required Function(PayFastResult result) onResult,
});

Parameters:

  • context: BuildContext for navigation
  • config: PayFastConfig instance with payment details
  • onResult: Callback function that receives PayFastResult

Flow:

  1. βœ“ Requests access token from PayFast API
  2. βœ“ Opens WebView on Android/iOS or a secure popup checkout on Web
  3. βœ“ Auto-submits form to PayFast
  4. βœ“ Monitors for success/failure redirects
  5. βœ“ Returns result via callback

PayFastResult

Result model returned from payment callback.

class PayFastResult {
  final bool success;           // Payment successful?
  final String message;         // Status message
  final String? transactionId;  // Transaction ID (if successful)
}

Example:

(result) {
  if (result.success) {
    print('Transaction: ${result.transactionId}');
    print('Message: ${result.message}');
  }
}

PayFastService

Low-level service for API communications.

getAccessToken()

Requests an access token from PayFast API.

final token = await PayFastService.getAccessToken(
  config: payFastConfig,
);

PayFastSignature

Utility for generating SHA256 signatures.

final signature = PayFastSignature.generate(
  securedKey: "XXXX",
  basketId: "ORDER123",
  amount: "100.00",
);

πŸ’‘ Advanced Usage

Complete Example

import 'package:flutter/material.dart';
import 'package:payfast_flutter/payfast_flutter.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PayFast SDK Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: PaymentDemo(),
    );
  }
}

class PaymentDemo extends StatefulWidget {
  @override
  State<PaymentDemo> createState() => _PaymentDemoState();
}

class _PaymentDemoState extends State<PaymentDemo> {
  String _paymentStatus = "Ready";
  PayFastResult? _lastResult;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('PayFast Payment Demo')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status Display
            Card(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  children: [
                    Text('Payment Status', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    SizedBox(height: 8),
                    Text(_paymentStatus),
                    if (_lastResult != null) ...[
                      SizedBox(height: 16),
                      Container(
                        padding: EdgeInsets.all(12),
                        decoration: BoxDecoration(
                          color: _lastResult!.success ? Colors.green[100] : Colors.red[100],
                          borderRadius: BorderRadius.circular(8),
                        ),
                        child: Column(
                          children: [
                            Text(
                              _lastResult!.success ? 'βœ“ Success' : 'βœ— Failed',
                              style: TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.bold,
                                color: _lastResult!.success ? Colors.green : Colors.red,
                              ),
                            ),
                            SizedBox(height: 8),
                            Text(_lastResult!.message),
                            if (_lastResult!.transactionId != null) ...[
                              SizedBox(height: 8),
                              Text('ID: ${_lastResult!.transactionId}', style: TextStyle(fontSize: 12)),
                            ]
                          ],
                        ),
                      ),
                    ]
                  ],
                ),
              ),
            ),
            SizedBox(height: 24),
            
            // Payment Button
            ElevatedButton.icon(
              onPressed: () => _initiatePayment(),
              icon: Icon(Icons.payment),
              label: Text('Start Payment'),
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(vertical: 16),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _initiatePayment() {
    final config = PayFastConfig(
      merchantId: "241665",
      securedKey: "XXXX",
      basketId: "ORD-${DateTime.now().millisecondsSinceEpoch}",
      amount: "100.00",
      successUrl: "https://example.com/success",
      failureUrl: "https://example.com/failure",
      checkoutUrl: "https://example.com/ipn",
      currency: "PKR",
      txnDesc: "Product Purchase",
      customerEmail: "customer@example.com",
      customerMobile: "03001234567",
    );

    setState(() => _paymentStatus = "Processing...");

    PayFast.startPayment(
      context: context,
      config: config,
      onResult: (result) {
        setState(() {
          _lastResult = result;
          _paymentStatus = result.success ? "Completed" : "Failed";
        });

        // Show snackbar
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(result.message),
            backgroundColor: result.success ? Colors.green : Colors.red,
          ),
        );
      },
    );
  }
}

Dynamic Amount Calculation

String calculateAmount(List<Item> items) {
  double total = items.fold(0, (sum, item) => sum + item.price);
  return total.toStringAsFixed(2);
}

final config = PayFastConfig(
  // ... other params ...
  amount: calculateAmount(cartItems),
);

Custom Error Handling

PayFast.startPayment(
  context: context,
  config: config,
  onResult: (result) {
    if (!result.success) {
      // Show custom error dialog
      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text('Payment Failed'),
          content: Text(result.message),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: Text('Retry'),
            ),
          ],
        ),
      );
    }
  },
);

🌐 Flutter Web Notes

Flutter Web is supported through a real HTML form submission to PayFast:

  • The SDK opens a popup window on user interaction.
  • It creates a hidden HTML form and POSTs the payment fields to PayFast.
  • The popup is polled until PayFast redirects back to your app.
  • The PayFast access token should be generated on your backend and supplied through accessTokenProvider.

Important web requirements

For Flutter Web:

  • successUrl and failureUrl must use the same origin as your web app.
  • Use accessTokenProvider to fetch the PayFast access token from your backend.
  • Do not expose your PayFast securedKey directly in browser requests in production.

Example when running locally:

final successUrl = Uri.base.resolve('/payfast-success').toString();
final failureUrl = Uri.base.resolve('/payfast-failure').toString();
final checkoutUrl = Uri.base.resolve('/payfast-ipn').toString();

PayFast.startPayment(
  context: context,
  config: config,
  accessTokenProvider: (config) async {
    final response = await http.post(
      Uri.parse('http://localhost:8080/api/payfast/access-token'),
      headers: const {'Content-Type': 'application/json'},
      body: jsonEncode({
        'merchantId': config.merchantId,
        'securedKey': config.securedKey,
        'basketId': config.basketId,
        'amount': config.amount,
        'currencyCode': config.currency,
      }),
    );

    final data = jsonDecode(response.body) as Map<String, dynamic>;
    return data['accessToken'] as String?;
  },
  onResult: (result) {
    // handle result
  },
);

If you use a different redirect domain on web, the browser will block the SDK from safely detecting the final redirect.

πŸ”’ Security

  • βœ“ SHA256 signature generation for request verification
  • βœ“ Token-based authentication
  • βœ“ Support for sandbox and live environments
  • βœ“ HTTPS-only communication
  • βœ“ No sensitive data stored locally
  • βœ“ Null-safe Dart code

🌍 Supported Currencies

Currently supported currencies:

  • PKR (Pakistani Rupee) - Default

More currencies can be added via configuration.

πŸ“± Platform Support

  • βœ… Android
  • βœ… iOS
  • βœ… Web
  • ⚠️ Windows: not implemented
  • ⚠️ macOS: not implemented

πŸ“– Examples

A complete example app is available in the example folder.

For Flutter Web, start the local token proxy first:

cd example
dart run tool/payfast_token_server.dart

Then, in a second terminal:

cd example
flutter run -d chrome

For mobile:

cd example
flutter run

πŸ› Troubleshooting

Issue: "Failed to get access token"

Solution: Check that your credentials are correct:

  • Verify merchantId
  • Verify securedKey
  • Ensure internet connectivity
  • Test in sandbox mode first
  • On Flutter Web, verify your backend accessTokenProvider or token proxy is running and returning ACCESS_TOKEN

Issue: Payment page doesn't load

Solution:

  • On Android/iOS, check WebView initialization and connectivity
  • On Web, make sure the browser allowed the payment popup
  • Verify the PayFast endpoint and token are valid
  • Check the browser console or app logs for errors

Issue: Success/Failure redirect not triggered

Solution:

  • Verify successUrl and failureUrl are correct
  • On Flutter Web, ensure both URLs use the same origin as Uri.base
  • Keep the redirect routes reachable by your app
  • Ensure proper URL encoding

πŸ“ Dependencies

  • flutter: sdk
  • http: ^1.2.0
  • webview_flutter: ^4.7.0
  • crypto: ^3.0.3

πŸ“„ API Endpoints

Purpose Endpoint
Get Token https://ipg1.apps.net.pk/Ecommerce/api/Transaction/GetAccessToken
Process Payment https://ipg1.apps.net.pk/Ecommerce/api/Transaction/PostTransaction

πŸ“„ License

MIT License - See LICENSE file for details

Copyright (c) 2026 Abdullah Ghaffar

🀝 Contributing

Contributions are welcome! Please feel free to submit pull requests.

πŸ’¬ Support

If this package helps you and you'd like to say thanks, or if you want to work with us on a custom project, here are the best ways to connect:

Replace YOUR_COFFEE_URL with your real public support link before publishing.

πŸŽ“ Learn More


Built with ❀️ for Flutter developers

Libraries

payfast_flutter