saafe_aa_sdk 1.0.0 copy "saafe_aa_sdk: ^1.0.0" to clipboard
saafe_aa_sdk: ^1.0.0 copied to clipboard

Flutter SDK for integrating SAAFE's secure authentication and financial experience

example/lib/main.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'dart:io' show Platform;
import 'package:saafe_aa_sdk/saafe_sdk.dart';
import 'package:http/http.dart' as http;

void main() async {
  // Initialize Flutter binding
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize SAAFE SDK with sandbox mode (default is true)
  await SaafeSdk.initialize(useSandbox: true);
  
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SAAFE SDK Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const SaafeDemo(),
    );
  }
}


class SaafeDemo extends StatefulWidget {
  const SaafeDemo({Key? key}) : super(key: key);

  @override
  State<SaafeDemo> createState() => SaafeDemoState();
}

class SaafeDemoState extends State<SaafeDemo> {
  String fi = '';
  String reqdate = '';
  String ecreq = '';

  final TextEditingController textEditingController = TextEditingController();
  final FocusNode focusNode = FocusNode();

  SaafeRedirectInstance? _redirectInstance;
  String _status = 'Not started';
  bool _isSupported = true;
  bool _useSandbox = true; // Default to sandbox environment
  bool _showCloseButton = true; // Default to show close button
  DisplayMode _displayMode = DisplayMode.bottomSheet; // Default to bottom sheet

  @override
  void initState() {
    super.initState();
    _checkPlatformSupport();
    setState(() {
      _status = 'Enter mobile number and click "Start SAAFE Flow"';
    });
    // Don't fetch data on init - wait for user to enter mobile number
  }

  void _checkPlatformSupport() {
    setState(() {
      _isSupported = !(kIsWeb || !(Platform.isAndroid || Platform.isIOS));
      if (!_isSupported) {
        _status = 'Platform not supported';
      }
    });
  }

  // Switch between sandbox and production environments
  Future<void> _toggleEnvironment() async {
    setState(() {
      _useSandbox = !_useSandbox;
      // Re-initialize the SDK with the new environment setting
      SaafeSdk.initialize(useSandbox: _useSandbox);
      _status = 'Switched to ${_useSandbox ? 'sandbox' : 'production'} environment';
    });
  }

  bool get isTenDigits => textEditingController.text.length == 10;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SAAFE SDK Demo'),
        actions: [
          // Add environment indicator and toggle button in the app bar
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: Center(
              child: Row(
                children: [
                  // Environment indicator text
                  Text(
                    _useSandbox ? 'SANDBOX' : 'PRODUCTION',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: _useSandbox ? Colors.amber : Colors.red,
                    ),
                  ),
                  // Toggle button
                  Switch(
                    value: _useSandbox,
                    activeColor: Colors.amber,
                    inactiveTrackColor: Colors.red.withOpacity(0.5),
                    onChanged: (value) => _toggleEnvironment(),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Status: $_status',
                style: const TextStyle(fontSize: 18),
              ),
              const SizedBox(height: 30),
              if (!_isSupported)
                const Text(
                  'This platform does not support WebView. SAAFE SDK requires Android or iOS.',
                  textAlign: TextAlign.center,
                  style: TextStyle(color: Colors.red, fontSize: 16),
                )
              else ...[
                // Environment selection card
                Card(
                  elevation: 2,
                  margin: const EdgeInsets.only(bottom: 20),
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Text(
                          'Environment',
                          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                        ),
                        const SizedBox(height: 8),
                        Row(
                          children: [
                            Switch(
                              value: _useSandbox,
                              activeColor: Colors.green,
                              onChanged: (value) => _toggleEnvironment(),
                            ),
                            const SizedBox(width: 8),
                            Text(
                              _useSandbox ? 'Sandbox' : 'Production',
                              style: TextStyle(
                                fontWeight: FontWeight.bold,
                                color: _useSandbox ? Colors.green : Colors.red,
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 4),
                        Text(
                          _useSandbox 
                              ? 'Using stage-redirection.saafe.in'
                              : 'Using app-redirection.saafe.in',
                          style: TextStyle(
                            fontSize: 12,
                            color: Colors.grey[600],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                
                // Display mode selection card
                Card(
                  elevation: 2,
                  margin: const EdgeInsets.only(bottom: 20),
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Text(
                          'Display Mode',
                          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                        ),
                        const SizedBox(height: 8),
                        Row(
                          children: [
                            Radio<DisplayMode>(
                              value: DisplayMode.bottomSheet,
                              groupValue: _displayMode,
                              onChanged: (DisplayMode? value) {
                                setState(() {
                                  _displayMode = value!;
                                });
                              },
                            ),
                            const Text('Bottom Sheet'),
                            const SizedBox(width: 20),
                            Radio<DisplayMode>(
                              value: DisplayMode.fullPage,
                              groupValue: _displayMode,
                              onChanged: (DisplayMode? value) {
                                setState(() {
                                  _displayMode = value!;
                                });
                              },
                            ),
                            const Text('Full Page'),
                          ],
                        ),
                        const SizedBox(height: 8),
                        Row(
                          children: [
                            Checkbox(
                              value: _showCloseButton,
                              onChanged: (bool? value) {
                                setState(() {
                                  _showCloseButton = value!;
                                });
                              },
                            ),
                            Expanded(
                              child: Text(
                                _displayMode == DisplayMode.fullPage 
                                  ? 'Show Close Button (Optional for full page)'
                                  : 'Show Close Button',
                              ),
                            ),
                          ],
                        ),
                        if (_displayMode == DisplayMode.fullPage)
                          Padding(
                            padding: const EdgeInsets.only(top: 4.0, left: 40.0),
                            child: Text(
                              'In full page mode, users can use system back button to exit',
                              style: TextStyle(
                                fontSize: 12,
                                color: Colors.grey[600],
                                fontStyle: FontStyle.italic,
                              ),
                            ),
                          ),
                        if (_displayMode == DisplayMode.bottomSheet)
                          Padding(
                            padding: const EdgeInsets.only(top: 4.0, left: 40.0),
                            child: Text(
                              'In bottom sheet mode, users can drag down, use close button, or system back button',
                              style: TextStyle(
                                fontSize: 12,
                                color: Colors.grey[600],
                                fontStyle: FontStyle.italic,
                              ),
                            ),
                          ),
                      ],
                    ),
                  ),
                ),
                
                TextField(
                  controller: textEditingController,
                  maxLength: 10,
                  keyboardType: TextInputType.phone,
                  decoration: InputDecoration(
                    labelText: 'Mobile Number',
                    hintText: 'Enter 10 digit mobile number',
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                    prefixIcon: const Icon(Icons.phone),
                    filled: true,
                    fillColor: Colors.grey[100],
                    counterText: '', // Hide character counter
                  ),
                  onChanged: (value) {
                    setState(() {
                      // Update UI when text changes
                    });
                  },
                ),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: isTenDigits ? _fetchAndStartFlow : null,
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.blueAccent,
                    minimumSize: const Size(200, 50),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                  ),
                  child: const Text('Start SAAFE Flow', style: TextStyle(color: Colors.white)),
                ),
                if (_redirectInstance != null)
                  Padding(
                    padding: const EdgeInsets.only(top: 16.0),
                    child: ElevatedButton(
                      onPressed: _closeFlow,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.redAccent,
                        minimumSize: const Size(200, 50),
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(12),
                        ),
                      ),
                      child: const Text('Close Flow',style: TextStyle(color: Colors.white),),
                    ),
                  ),
              ],
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _fetchAndStartFlow() async {
    setState(() {
      _status = 'Fetching data...';
    });

    try {
      // Get mobile number from text field
      final mobileNumber = textEditingController.text;

      // Step 1: Login to get JWT token
      setState(() {
        _status = 'Authenticating...';
      });

      const loginUrl = 'https://saafe-tsp-api.saafe.tech/api/login';
      final loginResponse = await http.post(
        Uri.parse(loginUrl),
        headers: {
          'Content-Type': 'application/json',
          'accept': 'application/json, text/plain, */*',
          'accept-language': 'en-US,en;q=0.9',
          'origin': 'https://dev.tsp.saafe.tech',
          'referer': 'https://dev.tsp.saafe.tech/',
          'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
        },
        body: jsonEncode({
          "email": "tspdev@saafe.tech",
          "password": "Sudharsansk99@"
        }),
      );

      print("Login API Response Status: ${loginResponse.statusCode}");
      if (kDebugMode) {
        print("Login API Response Body: ${loginResponse.body}");
      }

      if (loginResponse.statusCode != 200) {
        setState(() {
          _status = 'Login Error: ${loginResponse.statusCode}';
        });
        return;
      }

      final loginData = jsonDecode(loginResponse.body);
      final String jwtToken = loginData['data']?['access_token'] ?? 
                              loginData['access_token'] ?? 
                              loginData['token'] ?? '';
      
      if (jwtToken.isEmpty) {
        setState(() {
          _status = 'Login Error: No token received';
        });
        return;
      }

      if (kDebugMode) {
        print("JWT Token received: ${jwtToken.substring(0, 20)}...");
      }

      // Step 2: Generate consent using the JWT token
      setState(() {
        _status = 'Generating consent...';
      });

      const consentUrl = 'https://saafe-tsp-api.saafe.tech/api/consent';
      final consentResponse = await http.post(
        Uri.parse(consentUrl),
        headers: {
          'Content-Type': 'application/json',
          'accept': 'application/json, text/plain, */*',
          'accept-language': 'en-US,en;q=0.9',
          'authorization': 'Bearer $jwtToken',
          'origin': 'https://dev.tsp.saafe.tech',
          'referer': 'https://dev.tsp.saafe.tech/',
          'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
        },
        body: jsonEncode({
          "fair_use_id": "CT008",
          "aa_id": ["SAAFE-UAT"],
          "consent_types": ["PROFILE", "SUMMARY", "TRANSACTIONS"],
          "consent_start_from": "2025-06-16T18:30:00.000Z",
          "consent_expiry": "2026-06-17T18:29:59.999Z",
          "customer_id": null,
          "mobile_number": mobileNumber,
          "email": "shidiqadm@gmail.com",
          "fetch_type": ["PERIODIC"],
          "fi_types": [
            "DEPOSIT", "TERM_DEPOSIT", "RECURRING_DEPOSIT", "SIP", "CP",
            "GOVT_SECURITIES", "EQUITIES", "BONDS", "DEBENTURES", "ETF",
            "IDR", "CIS", "AIF", "INVIT", "REIT", "GSTR1_3B", "MUTUAL_FUNDS"
          ],
          "data_life_unit": "MONTH",
          "data_life_value": 1,
          "frequency_unit": "MONTH",
          "frequency_value": 45,
          "purpose_code": "102",
          "purpose_url": "https://api.rebit.org.in/aa/purpose/102.xml",
          "purpose_description": "Customer spending patterns, budget or other reportings",
          "purpose_category": "01",
          "save_as_template": false,
          "pan_number": null,
          "date_of_birth": null,
          "delivery_mode": ["url", "sms"],
          "fip_id": [],
          "consent_mode": "STORE",
          "fi_datarange_from": "2015-06-16T18:30:00.000Z",
          "fi_datarange_to": "2025-06-17T18:29:59.999Z"
        }),
      );

      print("Consent API Response Status: ${consentResponse.statusCode}");
      if (kDebugMode) {
        print("Consent API Response Body: ${consentResponse.body}");
        print("Using mobile number: $mobileNumber");
      }

      if (consentResponse.statusCode == 200 || consentResponse.statusCode == 201) {
        final jsonResponse = jsonDecode(consentResponse.body);
        if (kDebugMode) {
          print("Parsed JSON Response: $jsonResponse");
        }

        // Check if the API call was actually successful
        final bool isSuccess = jsonResponse['success'] == true;
        final String status = jsonResponse['status'] ?? '';
        
        if (!isSuccess || status == 'error') {
          setState(() {
            _status = 'Consent API Error: ${jsonResponse['message'] ?? 'Unknown error'}';
          });
          if (kDebugMode) {
            print('API returned error status: $status');
            print('Error message: ${jsonResponse['message']}');
          }
          return;
        }

        // Extract parameters from the new API response structure
        // The response structure might be different, so we need to adapt
        setState(() {
          // Try to extract from different possible response structures
          fi = jsonResponse['data']?['encode']?['fi'] ?? 
               jsonResponse['fi'] ?? 
               jsonResponse['data']?['fi'] ?? 
               jsonResponse['consent_handle'] ?? 
               jsonResponse['consentHandle'] ?? '';
          
          reqdate = jsonResponse['data']?['encode']?['reqdate'] ?? 
                   jsonResponse['reqdate'] ?? 
                   jsonResponse['data']?['reqdate'] ?? 
                   jsonResponse['data']?['date'] ?? 
                   jsonResponse['timestamp'] ?? 
                   DateTime.now().millisecondsSinceEpoch.toString();
          
          ecreq = jsonResponse['data']?['encode']?['ecreq'] ?? 
                 jsonResponse['ecreq'] ?? 
                 jsonResponse['data']?['ecreq'] ?? 
                 jsonResponse['encrypted_consent'] ?? 
                 jsonResponse['consent_data'] ?? '';
        });

        print('Extracted values:\nfi: $fi\nreqdate: $reqdate\necreq: $ecreq');
        
        // Check if we have valid parameters from the API
        if (fi.isEmpty || reqdate.isEmpty || ecreq.isEmpty) {
          setState(() {
            _status = 'API Error: Missing parameters from response. Please check the API response structure.';
          });
          if (kDebugMode) {
            print('Response structure: ${jsonResponse.keys.toList()}');
          }
          return;
        }
        
        // Now start the SAAFE flow with the fetched parameters
        _startSaafeFlow();
      } else {
        if (kDebugMode) {
          print('Failed to generate consent. Status code: ${consentResponse.statusCode}');
        }
        setState(() {
          _status = 'Consent API Error: ${consentResponse.statusCode}';
        });
      }
    } catch (e) {
      if (kDebugMode) {
        print('Error in API calls: $e');
      }
      setState(() {
        _status = 'API Error: $e';
      });
    }
  }

  Future<void> _startSaafeFlow() async {
    setState(() {
      _status = 'Starting...';
    });

    if (kDebugMode) {
      print("Starting SAAFE flow with: fi=$fi, reqdate=$reqdate, ecreq=$ecreq");
      print("Display mode: $_displayMode, Show close button: $_showCloseButton");
      print("Timestamp of parameters: ${DateTime.now()}");
    }
    
    final options = SaafeRedirectOptions(
      fi: fi,
      reqdate: reqdate,
      ecreq: ecreq,
      showCloseButton: _showCloseButton,
      displayMode: _displayMode,
      onLoad: () {
        if (kDebugMode) {
          print("WebView onLoad callback fired");
        }
        setState(() {
          _status = 'Flow loaded';
        });
      },
      onComplete: (data) {
        if (kDebugMode) {
          print("WebView onComplete callback fired with data: $data");
        }
        setState(() {
          _status = 'Completed successfully! Data: ${data.toString()}';
          _redirectInstance = null;
        });
        
        // Client app decides when to close the SDK
        // Navigator.of(context).pop(); // Close the SDK
        
        // Here you can navigate to a specific screen based on the completion data
        // For example: Navigator.pushNamed(context, '/success-screen');
      },
      onCancel: () {
        if (kDebugMode) {
          print("WebView onCancel callback fired");
        }
        setState(() {
          _status = 'Cancelled by user';
          _redirectInstance = null;
        });
        
        // Client app decides when to close the SDK
        Navigator.of(context).pop(); // Close the SDK
        
        // Here you can navigate to a specific screen or just stay on current screen
      },
      onError: (error) {
        if (kDebugMode) {
          print("WebView onError callback fired with error: $error");
        }
        setState(() {
          _status = 'Error: $error';
          _redirectInstance = null;
        });
        
        // Client app decides when to close the SDK
        // Navigator.of(context).pop(); // Close the SDK
        
        // Here you can show an error dialog or navigate to an error screen
        // For example: _showErrorDialog(error);
      },
    );

    _redirectInstance = await SaafeSdk.triggerRedirect(context, options);
  }

  void _closeFlow() {
    _redirectInstance?.close();
    setState(() {
      _status = 'Closed by app';
      _redirectInstance = null;
    });
  }
}
2
likes
0
points
118
downloads

Documentation

Documentation

Publisher

unverified uploader

Weekly Downloads

Flutter SDK for integrating SAAFE's secure authentication and financial experience

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

cupertino_icons, flutter, http, url_launcher, webview_flutter

More

Packages that depend on saafe_aa_sdk