Flutter Hosted Payments

Hosted payment integration with YagoutPay in Flutter applications using WebView for seamless payment processing.

Hosted payments redirect customers to YagoutPay's secure payment page using WebView integration. This method provides a seamless checkout experience with minimal integration effort and automatic success/failure detection.

Overview

Hosted payments use YagoutPay's secure payment page to process transactions. The process involves encrypting payment data, generating an HTML form, and redirecting users to YagoutPay's hosted payment page using WebView.

Hosted Payment Flow

  1. Data Collection: Collect payment data from customer form
  2. Data Structure: Build complete payment structure with all required fields
  3. Encryption: Encrypt payment data using AES-256-CBC with manual padding
  4. Hash Generation: Generate SHA-512 hash for security
  5. Form Generation: Create HTML form with encrypted data
  6. WebView Integration: Display form in WebView for automatic submission
  7. Result Handling: Handle success/failure callbacks

Encryption Service

Create a service to handle AES-256-CBC encryption with manual padding for hosted payments:

Example Hosted Encryption Service with Flutter:

// yagoutpay_hosted_encryption_service.dart
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:encrypt/encrypt.dart';

class YagoutPayHostedEncryptionService {
  final String merchantId;
  final String encryptionKey;
  final String iv = '0123456789abcdef'; // Fixed 16-byte IV
  
  YagoutPayHostedEncryptionService({
    required this.merchantId,
    required this.encryptionKey,
  });
  
  // AES-256-CBC Encryption for Hosted Payments with Manual Padding
  String encrypt(String text) {
    try {
      final key = Key.fromBase64(encryptionKey);
      final ivBytes = IV.fromUtf8(iv);
      
      // Manual padding for hosted payments
      final size = 16;
      final pad = size - (text.length % size);
      final padtext = text + String.fromCharCode(pad).repeat(pad);
      
      final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: null));
      final encrypted = encrypter.encrypt(padtext, iv: ivBytes);
      return encrypted.base64;
    } catch (e) {
      throw Exception('Encryption failed: $e');
    }
  }
  
  // AES-256-CBC Decryption for Response Handling
  String decrypt(String encryptedData) {
    try {
      final key = Key.fromBase64(encryptionKey);
      final ivBytes = IV.fromUtf8(iv);
      final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: null));
      
      final encrypted = Encrypted.fromBase64(encryptedData);
      final decrypted = encrypter.decrypt(encrypted, iv: ivBytes);
      
      // Remove padding
      final pad = decrypted.codeUnitAt(decrypted.length - 1);
      if (pad > decrypted.length) {
        throw Exception('Invalid padding');
      }
      
      return decrypted.substring(0, decrypted.length - pad);
    } catch (e) {
      throw Exception('Decryption failed: $e');
    }
  }
  
  // Generate SHA-512 Hash for Hosted Payments
  String generateHash(String data, String saltKey) {
    final bytes = utf8.encode(data + saltKey);
    final digest = sha512.convert(bytes);
    return digest.toString();
  }
}

Hosted Payment Service

Create a service to handle hosted payment processing:

Example Hosted Payment Service with Flutter:

// yagoutpay_hosted_service.dart
import 'dart:convert';
import 'yagoutpay_hosted_encryption_service.dart';

class YagoutPayHostedService {
  final YagoutPayHostedEncryptionService encryptionService;
  final String merchantId;
  final String gatewayUrl;
  final String saltKey;
  
  YagoutPayHostedService({
    required this.merchantId,
    required String encryptionKey,
    required this.gatewayUrl,
    required this.saltKey,
  }) : encryptionService = YagoutPayHostedEncryptionService(
         merchantId: merchantId,
         encryptionKey: encryptionKey,
       );
  
  // Build Complete Hosted Payment Data Structure
  Map buildHostedPaymentData({
    required String orderNo,
    required String amount,
    required String email,
    required String mobile,
    required String successUrl,
    required String failureUrl,
    String? customerName,
    String? billAddress,
    String? billCity,
    String? billState,
    String? billCountry,
    String? billZip,
    String? shipAddress,
    String? shipCity,
    String? shipState,
    String? shipCountry,
    String? shipZip,
  }) {
    return {
      'txn_details': {
        'ag_id': 'yagout',
        'me_id': merchantId,
        'order_no': orderNo,
        'amount': amount,
        'country': 'ETH',
        'currency': 'ETB',
        'txn_type': 'SALE',
        'success_url': successUrl,
        'failure_url': failureUrl,
        'channel': 'MOBILE',
      },
      'pg_details': {
        'pg_id': '',
        'paymode': '',
        'scheme_id': '',
        'wallet_type': 'telebirr',
      },
      'card_details': {
        'card_no': '',
        'exp_month': '',
        'exp_year': '',
        'cvv': '',
      },
      'cust_details': {
        'card_name': '',
        'cust_name': customerName ?? '',
        'customer_email': email,
        'mobile_no': mobile,
        'unique_id': '',
        'is_logged_in': 'Y',
      },
      'bill_details': {
        'bill_addres': billAddress ?? 'N/A',
        'bill_city': billCity ?? 'Addis Ababa',
        'bill_state': billState ?? 'Addis Ababa',
        'bill_country': billCountry ?? 'ET',
        'bill_zip': billZip ?? '1000',
      },
      'ship_details': {
        'ship_address': shipAddress ?? 'N/A',
        'ship_city': shipCity ?? 'Addis Ababa',
        'ship_state': shipState ?? 'Addis Ababa',
        'ship_country': shipCountry ?? 'ET',
        'ship_zip': shipZip ?? '1000',
        'ship_days': '1',
        'address_count': '1',
      },
      'item_details': {
        'item_count': '1',
        'item_value': amount,
        'item_category': 'Payment',
      },
      'upi_details': {
        'udf_1': '',
        'udf_2': '',
        'udf_3': '',
        'udf_4': '',
        'udf_5': '',
      },
    };
  }
  
  // Generate Hosted Payment HTML Form
  Future> generateHostedPayment({
    required String orderNo,
    required String amount,
    required String email,
    required String mobile,
    required String successUrl,
    required String failureUrl,
    String? customerName,
    String? billAddress,
    String? billCity,
    String? billState,
    String? billCountry,
    String? billZip,
    String? shipAddress,
    String? shipCity,
    String? shipState,
    String? shipCountry,
    String? shipZip,
  }) async {
    try {
      // Step 1: Build payment data structure
      final paymentData = buildHostedPaymentData(
        orderNo: orderNo,
        amount: amount,
        email: email,
        mobile: mobile,
        successUrl: successUrl,
        failureUrl: failureUrl,
        customerName: customerName,
        billAddress: billAddress,
        billCity: billCity,
        billState: billState,
        billCountry: billCountry,
        billZip: billZip,
        shipAddress: shipAddress,
        shipCity: shipCity,
        shipState: shipState,
        shipCountry: shipCountry,
        shipZip: shipZip,
      );
      
      // Step 2: Build pipe-separated string for encryption
      final txnDetails = [
        paymentData['txn_details']['ag_id'],
        paymentData['txn_details']['me_id'],
        paymentData['txn_details']['order_no'],
        paymentData['txn_details']['amount'],
        paymentData['txn_details']['country'],
        paymentData['txn_details']['currency'],
        paymentData['txn_details']['txn_type'],
        paymentData['txn_details']['success_url'],
        paymentData['txn_details']['failure_url'],
        paymentData['txn_details']['channel'],
      ].join('|');
      
      final pgDetails = [
        paymentData['pg_details']['pg_id'],
        paymentData['pg_details']['paymode'],
        paymentData['pg_details']['scheme_id'],
        paymentData['pg_details']['wallet_type'],
      ].join('|');
      
      final cardDetails = [
        paymentData['card_details']['card_no'],
        paymentData['card_details']['exp_month'],
        paymentData['card_details']['exp_year'],
        paymentData['card_details']['cvv'],
      ].join('|');
      
      final custDetails = [
        paymentData['cust_details']['card_name'],
        paymentData['cust_details']['cust_name'],
        paymentData['cust_details']['customer_email'],
        paymentData['cust_details']['mobile_no'],
        paymentData['cust_details']['unique_id'],
        paymentData['cust_details']['is_logged_in'],
      ].join('|');
      
      final billDetails = [
        paymentData['bill_details']['bill_addres'],
        paymentData['bill_details']['bill_city'],
        paymentData['bill_details']['bill_state'],
        paymentData['bill_details']['bill_country'],
        paymentData['bill_details']['bill_zip'],
      ].join('|');
      
      final shipDetails = [
        paymentData['ship_details']['ship_address'],
        paymentData['ship_details']['ship_city'],
        paymentData['ship_details']['ship_state'],
        paymentData['ship_details']['ship_country'],
        paymentData['ship_details']['ship_zip'],
        paymentData['ship_details']['ship_days'],
        paymentData['ship_details']['address_count'],
      ].join('|');
      
      final itemDetails = [
        paymentData['item_details']['item_count'],
        paymentData['item_details']['item_value'],
        paymentData['item_details']['item_category'],
      ].join('|');
      
      final upiDetails = [
        paymentData['upi_details']['udf_1'],
        paymentData['upi_details']['udf_2'],
        paymentData['upi_details']['udf_3'],
        paymentData['upi_details']['udf_4'],
        paymentData['upi_details']['udf_5'],
      ].join('|');
      
      // Step 3: Combine all sections with tildes
      final allValues = [
        txnDetails,
        pgDetails,
        cardDetails,
        custDetails,
        billDetails,
        shipDetails,
        itemDetails,
        upiDetails,
      ].join('~');
      
      // Step 4: Encrypt the combined string
      final encryptedData = encryptionService.encrypt(allValues);
      
      // Step 5: Generate SHA-512 hash
      final hash = encryptionService.generateHash(allValues, saltKey);
      
      // Step 6: Generate HTML form
      final html = generateHtmlForm(encryptedData, hash);
      
      return {
        'success': true,
        'html': html,
        'orderId': orderNo,
        'encryptedData': encryptedData,
        'hash': hash,
      };
    } catch (e) {
      return {
        'success': false,
        'error': 'Hosted payment generation failed: $e',
      };
    }
  }
  
  // Generate HTML Form for Auto-Submission
  String generateHtmlForm(String encryptedData, String hash) {
    return '''
    
    
      
        Redirecting to YagoutPay...
        
      
      
        

Redirecting to YagoutPay...

Please wait while we redirect you to the secure payment page.

'''; } }

WebView Integration

Create a WebView screen to handle hosted payment processing:

Example WebView Screen with Flutter:

// yagoutpay_webview_screen.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'yagoutpay_hosted_service.dart';

class YagoutPayWebViewScreen extends StatefulWidget {
  final String orderNo;
  final String amount;
  final String email;
  final String mobile;
  final String successUrl;
  final String failureUrl;
  final String? customerName;
  final String? billAddress;
  final String? billCity;
  final String? billState;
  final String? billCountry;
  final String? billZip;
  
  const YagoutPayWebViewScreen({
    Key? key,
    required this.orderNo,
    required this.amount,
    required this.email,
    required this.mobile,
    required this.successUrl,
    required this.failureUrl,
    this.customerName,
    this.billAddress,
    this.billCity,
    this.billState,
    this.billCountry,
    this.billZip,
  }) : super(key: key);
  
  @override
  _YagoutPayWebViewScreenState createState() => _YagoutPayWebViewScreenState();
}

class _YagoutPayWebViewScreenState extends State {
  late WebViewController _controller;
  bool _isLoading = true;
  String? _error;
  
  @override
  void initState() {
    super.initState();
    _initializeWebView();
  }
  
  void _initializeWebView() async {
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (String url) {
            setState(() {
              _isLoading = true;
            });
          },
          onPageFinished: (String url) {
            setState(() {
              _isLoading = false;
            });
          },
          onNavigationRequest: (NavigationRequest request) {
            // Handle success/failure URLs
            if (request.url.contains('success') || request.url.contains('failure')) {
              _handlePaymentResult(request.url);
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      );
    
    await _loadPaymentForm();
  }
  
  Future _loadPaymentForm() async {
    try {
      final hostedService = YagoutPayHostedService(
        merchantId: 'YOUR_MERCHANT_ID',
        encryptionKey: 'YOUR_ENCRYPTION_KEY',
        gatewayUrl: 'https://uatcheckout.yagoutpay.com/ms-transaction-core-1-0/paymentRedirection/checksumGatewayPage',
        saltKey: 'YOUR_SALT_KEY',
      );
      
      final result = await hostedService.generateHostedPayment(
        orderNo: widget.orderNo,
        amount: widget.amount,
        email: widget.email,
        mobile: widget.mobile,
        successUrl: widget.successUrl,
        failureUrl: widget.failureUrl,
        customerName: widget.customerName,
        billAddress: widget.billAddress,
        billCity: widget.billCity,
        billState: widget.billState,
        billCountry: widget.billCountry,
        billZip: widget.billZip,
      );
      
      if (result['success']) {
        await _controller.loadHtmlString(result['html']);
      } else {
        setState(() {
          _error = result['error'];
        });
      }
    } catch (e) {
      setState(() {
        _error = 'Failed to load payment form: $e';
      });
    }
  }
  
  void _handlePaymentResult(String url) {
    if (url.contains('success')) {
      Navigator.of(context).pop({'success': true, 'url': url});
    } else if (url.contains('failure')) {
      Navigator.of(context).pop({'success': false, 'url': url});
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('YagoutPay Payment'),
        backgroundColor: Colors.blue,
        leading: IconButton(
          icon: Icon(Icons.close),
          onPressed: () => Navigator.of(context).pop({'success': false, 'cancelled': true}),
        ),
      ),
      body: _error != null
          ? Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.error, color: Colors.red, size: 64),
                  SizedBox(height: 16),
                  Text(
                    'Payment Error',
                    style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  Text(
                    _error!,
                    textAlign: TextAlign.center,
                    style: TextStyle(color: Colors.red),
                  ),
                  SizedBox(height: 16),
                  ElevatedButton(
                    onPressed: () => Navigator.of(context).pop({'success': false, 'error': _error}),
                    child: Text('Close'),
                  ),
                ],
              ),
            )
          : Stack(
              children: [
                WebViewWidget(controller: _controller),
                if (_isLoading)
                  Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        CircularProgressIndicator(),
                        SizedBox(height: 16),
                        Text('Loading payment page...'),
                      ],
                    ),
                  ),
              ],
            ),
    );
  }
}

Payment Controller

Create a controller to handle hosted payment flow:

Example Hosted Payment Controller with Flutter:

// hosted_payment_controller.dart
import 'package:flutter/material.dart';
import 'yagoutpay_hosted_service.dart';
import 'yagoutpay_webview_screen.dart';

class HostedPaymentController extends ChangeNotifier {
  final YagoutPayHostedService hostedService;
  bool _isLoading = false;
  String? _error;
  
  HostedPaymentController({
    required String merchantId,
    required String encryptionKey,
    required String gatewayUrl,
    required String saltKey,
  }) : hostedService = YagoutPayHostedService(
         merchantId: merchantId,
         encryptionKey: encryptionKey,
         gatewayUrl: gatewayUrl,
         saltKey: saltKey,
       );
  
  bool get isLoading => _isLoading;
  String? get error => _error;
  
  // Process Hosted Payment
  Future> processHostedPayment({
    required String orderNo,
    required String amount,
    required String email,
    required String mobile,
    required String successUrl,
    required String failureUrl,
    String? customerName,
    String? billAddress,
    String? billCity,
    String? billState,
    String? billCountry,
    String? billZip,
    String? shipAddress,
    String? shipCity,
    String? shipState,
    String? shipCountry,
    String? shipZip,
  }) async {
    try {
      _setLoading(true);
      _clearError();
      
      // Validate payment data
      final validation = _validatePaymentData(
        amount: amount,
        email: email,
        mobile: mobile,
        orderNo: orderNo,
      );
      
      if (!validation['isValid']) {
        _setError('Validation failed: ${validation['errors']}');
        return {'success': false, 'error': _error};
      }
      
      // Generate hosted payment
      final result = await hostedService.generateHostedPayment(
        orderNo: orderNo,
        amount: amount,
        email: email,
        mobile: mobile,
        successUrl: successUrl,
        failureUrl: failureUrl,
        customerName: customerName,
        billAddress: billAddress,
        billCity: billCity,
        billState: billState,
        billCountry: billCountry,
        billZip: billZip,
        shipAddress: shipAddress,
        shipCity: shipCity,
        shipState: shipState,
        shipCountry: shipCountry,
        shipZip: shipZip,
      );
      
      if (result['success']) {
        _clearError();
        return {
          'success': true,
          'html': result['html'],
          'orderId': result['orderId'],
        };
      } else {
        _setError(result['error']);
        return {'success': false, 'error': result['error']};
      }
    } catch (e) {
      _setError('Hosted payment processing failed: $e');
      return {'success': false, 'error': _error};
    } finally {
      _setLoading(false);
    }
  }
  
  // Validate Payment Data
  Map _validatePaymentData({
    required String amount,
    required String email,
    required String mobile,
    required String orderNo,
  }) {
    final errors = {};
    bool isValid = true;
    
    if (amount.isEmpty || double.tryParse(amount) == null || double.parse(amount) <= 0) {
      errors['amount'] = 'Amount is required and must be greater than 0';
      isValid = false;
    }
    
    if (email.isEmpty || !RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$').hasMatch(email)) {
      errors['email'] = 'Valid email is required';
      isValid = false;
    }
    
    if (mobile.isEmpty) {
      errors['mobile'] = 'Mobile number is required';
      isValid = false;
    }
    
    if (orderNo.isEmpty) {
      errors['orderNo'] = 'Order number is required';
      isValid = false;
    }
    
    return {
      'isValid': isValid,
      'errors': errors,
    };
  }
  
  void _setLoading(bool loading) {
    _isLoading = loading;
    notifyListeners();
  }
  
  void _setError(String error) {
    _error = error;
    notifyListeners();
  }
  
  void _clearError() {
    _error = null;
    notifyListeners();
  }
}

Key Implementation Points

  • Encryption: Use AES-256-CBC with manual padding for hosted payments
  • Hash Generation: Generate SHA-512 hash for security validation
  • Form Submission: Auto-submit HTML form to YagoutPay gateway
  • WebView Integration: Use WebView to display payment page
  • URL Handling: Monitor navigation for success/failure URLs
  • Error Handling: Implement proper error handling for network issues

NEXT STEPS

After implementing hosted payments, explore Payment Links for generating shareable payment URLs.