Remit2Any Auth Flutter SDK

A comprehensive Flutter package for seamless AWS Cognito authentication via optimized WebViews for Remit2Any. This package provides enhanced WebView components with advanced keyboard handling, permission management, and secure token storage.

✨ Features

πŸ” Authentication & Security

  • WebView-based authentication with Remit2Any Cognito integration
  • Secure token storage using flutter_secure_storage with memory caching
  • Automatic token refresh with background refresh capabilities
  • User profile management with Cognito user data fetching
  • Session management with automatic logout detection
  • Force WebView authentication option to skip token refresh
  • Logout with redirect to login for seamless re-authentication
  • Referral code support for mypursu tenant signup flow
  • Payment integration with Remit2Any Pay API for seamless payment processing

πŸ“± Enhanced WebView Experience

  • Advanced keyboard handling for iOS and Android
  • Smart input field scrolling to prevent keyboard overlap
  • Automatic permission requests for camera and microphone (KYC processes)
  • WhatsApp URL handling with external browser redirect
  • Multi-domain support with intelligent URL routing
  • File download capabilities with native file manager integration
  • Payment flow integration with event handling for payment completion and cancellation
  • Flexible domain allowlist with support for additional allowed hosts

πŸ›‘οΈ Robust Permission Management

  • Dynamic permission handling for US KYC and KYC documents
  • Platform-specific optimizations for iOS and Android
  • Secure domain validation for WebView navigation
  • Cross-platform compatibility with consistent behavior

🌐 Flexible Environment Management

  • Development and production environment support
  • Configurable URLs and settings per environment
  • Custom user agent management for WebView identification

πŸš€ Installation

Add to your pubspec.yaml:

dependencies:
  remit2any_auth: ^0.1.8

Run:

flutter pub get

πŸ“‹ Required Permissions

Android

Add to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

iOS

Add to ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>This app needs camera access to capture documents for identity verification and KYC compliance.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access for identity verification processes and KYC compliance.</string>
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

βš™οΈ Configuration

Environment Setup

Configure your environment in your app initialization:

import 'package:remit2any_auth/remit2any_auth.dart';

void main() {
  // Set environment (dev or prod)
  Remit2AnyEnvironmentConfig.setEnvironment(Environment.prod);
  
  // Optional: Set custom user agent suffix
  Remit2AnyEnvironmentConfig.setUserAgentSuffix('embeddablewidget');
  
  runApp(MyApp());
}

Supported Environments

  • Development: Environment.dev β†’ https://dev.remit2any.com
  • Production: Environment.prod β†’ https://remit2any.com

πŸ“– Complete Integration Guide

1. πŸš€ Library Setup

First, configure your environment in your app initialization:

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

void main() {
  // Set environment based on build configuration
  Remit2AnyEnvironmentConfig.setEnvironment(Environment.dev); // or Environment.prod
  
  // Set user agent suffix for WebView identification
  Remit2AnyEnvironmentConfig.setUserAgentSuffix('embeddablewidget');
  
  runApp(MyApp());
}

2. πŸ” Sign In Authentication

Use getAccessTokenWithAuth() for sign-in as it handles authentication automatically. You can optionally provide an email and referral code to pre-fill the authentication form:

import 'package:remit2any_auth/remit2any_auth.dart';

class AuthService {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Sign in using getAccessTokenWithAuth (Recommended)
  /// This method automatically handles authentication if needed
  Future<String?> signIn(BuildContext context, {String? email, String? referralCode, bool forceWebView = false}) async {
    try {
      final accessToken = await _auth.getAccessTokenWithAuth(context, email: email, referralCode: referralCode, forceWebView: forceWebView);
      
      if (accessToken != null) {
        print('βœ… Successfully signed in');
        print('Access token length: ${accessToken.length}');
        return accessToken;
      } else {
        print('❌ User cancelled authentication');
        return null;
      }
    } catch (e) {
      print('❌ Sign-in error: $e');
      return null;
    }
  }

}

πŸ“§ Email and Referral Code Parameter Support

The getAccessTokenWithAuth() method now supports optional email and referral code parameters to improve user experience:

class EmailAuthExample {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Sign in without email (existing behavior)
  Future<String?> signInWithoutEmail(BuildContext context) async {
    return await _auth.getAccessTokenWithAuth(context);
  }

  /// Sign in with email pre-fill (new feature)
  Future<String?> signInWithEmail(BuildContext context, String email) async {
    return await _auth.getAccessTokenWithAuth(context, email: email);
  }

  /// Sign in with referral code (for mypursu tenant)
  Future<String?> signInWithReferralCode(BuildContext context, String referralCode) async {
    return await _auth.getAccessTokenWithAuth(context, referralCode: referralCode);
  }

  /// Sign in with both email and referral code
  Future<String?> signInWithEmailAndReferral(BuildContext context, String email, String referralCode) async {
    return await _auth.getAccessTokenWithAuth(context, email: email, referralCode: referralCode);
  }

  /// Example: Sign in with user's stored email
  Future<String?> signInWithStoredEmail(BuildContext context) async {
    // Get stored email from previous session
    final storedEmail = await _auth.getStoredEmail();
    
    if (storedEmail != null && storedEmail.isNotEmpty) {
      print('πŸ“§ Using stored email: $storedEmail');
      return await _auth.getAccessTokenWithAuth(context, email: storedEmail);
    } else {
      print('πŸ“§ No stored email, signing in without pre-fill');
      return await _auth.getAccessTokenWithAuth(context);
    }
  }
}

Benefits of Email Parameter:

  • βœ… Pre-fills email field in the authentication form
  • βœ… Faster user experience - users don't need to type their email again
  • βœ… Reduces typos in email entry
  • βœ… Seamless re-authentication using stored email from previous sessions

Benefits of Referral Code Parameter:

  • βœ… Pre-fills referral code in the signup form (for mypursu tenant)
  • βœ… Streamlined signup flow - automatically routes to /auth/register-widget when referral code is provided
  • βœ… Better user tracking - enables referral program functionality

πŸš€ Force WebView Authentication

The getAccessTokenWithAuth() method now supports a forceWebView parameter to skip token refresh and go directly to WebView authentication:

class ForceWebViewExample {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Force WebView authentication (skip token refresh)
  Future<String?> forceWebViewAuth(BuildContext context, {String? email, String? referralCode}) async {
    return await _auth.getAccessTokenWithAuth(
      context, 
      email: email,
      referralCode: referralCode,
      forceWebView: true  // Skip token refresh, go directly to WebView
    );
  }

  /// Normal authentication (try token refresh first)
  Future<String?> normalAuth(BuildContext context, {String? email, String? referralCode}) async {
    return await _auth.getAccessTokenWithAuth(
      context, 
      email: email,
      referralCode: referralCode,
      forceWebView: false  // Default behavior - try token refresh first
    );
  }
}

When to use forceWebView: true:

  • βœ… Fresh authentication - when you want to ensure a clean login
  • βœ… Token issues - when existing tokens are corrupted or invalid
  • βœ… Security requirements - when you need to force re-authentication
  • βœ… Testing scenarios - when you want to test the full login flow

πŸ”„ Logout with Redirect to Login

The signOut() method now supports automatic redirect to login after logout:

class LogoutExample {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Normal logout (existing behavior)
  Future<void> normalLogout(BuildContext context) async {
    await _auth.signOut(context);
  }

  /// Logout with automatic redirect to login
  Future<void> logoutWithRedirect(BuildContext context) async {
    await _auth.signOut(context, redirectToLogin: true);
  }
}

Benefits of redirectToLogin: true:

  • βœ… Seamless user experience - no need to manually trigger login after logout
  • βœ… Automatic re-authentication - user can immediately sign in again
  • βœ… Reduced friction - eliminates the need for separate login calls
  • βœ… Consistent flow - maintains authentication state automatically

3. πŸ”‘ Get Access Token

Always check for existing tokens before requiring re-authentication:

class TokenManager {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Get access token without UI (context-free)
  Future<String?> getAccessToken() async {
    try {
      final accessToken = await _auth.getAccessToken();
      
      if (accessToken != null) {
        print('βœ… Valid access token found');
        return accessToken;
      } else {
        print('❌ No valid access token - authentication required');
        return null;
      }
    } catch (e) {
      print('Error getting access token: $e');
      return null;
    }
  }

  /// Complete token flow with automatic re-authentication
  Future<String?> getAccessTokenWithFallback(BuildContext context, {String? email}) async {
    // Step 1: Try to get existing token
    String? token = await getAccessToken();
    
    // Step 2: If null, authenticate user
    if (token == null) {
      print('πŸ”„ Access token is null, initiating sign-in...');
      token = await _auth.getAccessTokenWithAuth(context, email: email);
    }
    
    return token;
  }
}

4. ⚠️ Handle Null Access Token

Important: If getAccessToken() returns null, you MUST call sign-in again:

class ApiService {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  Future<void> makeApiCall(BuildContext context, {String? email}) async {
    // Get access token
    String? accessToken = await _auth.getAccessToken();
    
    // Check if token is null
    if (accessToken == null) {
      print('🚨 Access token is null - user needs to sign in');
      
      // Trigger sign-in process with optional email
      accessToken = await _auth.getAccessTokenWithAuth(context, email: email);
      
      if (accessToken == null) {
        print('❌ User cancelled sign-in');
        return; // Exit if user cancels
      }
    }
    
    // Now use the access token for API calls
    await _callApiWithToken(accessToken);
  }

  Future<void> _callApiWithToken(String accessToken) async {
    // Your API call implementation
    print('🌐 Making API call with token: ${accessToken.substring(0, 20)}...');
  }
}

5. 🌐 Open WebViews

All webview methods return a result map that you should check for logout status:

class WebViewService {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Open full site with custom tenant
  Future<void> openFullSite(BuildContext context, {String? tenant}) async {
    final result = await _auth.openFullSite(context, tenant: tenant);
    _handleWebViewResult(context, result, 'Full Site');
  }

  /// Open transfer page
  Future<void> openTransfer(BuildContext context, {double usdAmount = 100.0}) async {
    final result = await _auth.openTransfer(context, usdAmount: usdAmount);
    _handleWebViewResult(context, result, 'Transfer');
  }

  /// Open wallet deposit
  Future<void> openWalletDeposit(BuildContext context) async {
    final result = await _auth.openWalletDeposit(context);
    _handleWebViewResult(context, result, 'Wallet Deposit');
  }

  /// Open transactions
  Future<void> openTransactions(BuildContext context) async {
    final result = await _auth.openTransactions(context);
    _handleWebViewResult(context, result, 'Transactions');
  }

  /// Open US KYC documents
  Future<void> openUsKyc(BuildContext context) async {
    final result = await _auth.openUsKyc(context);
    _handleWebViewResult(context, result, 'US KYC');
  }

  /// Open Indian KYC documents
  Future<void> openIndianKyc(BuildContext context) async {
    final result = await _auth.openIndianKyc(context);
    _handleWebViewResult(context, result, 'Indian KYC');
  }

  /// Open bank accounts
  Future<void> openBankAccounts(BuildContext context) async {
    final result = await _auth.openBankAccounts(context);
    _handleWebViewResult(context, result, 'Bank Accounts');
  }

  /// Open US-US transfers
  Future<void> openUsUsTransfers(BuildContext context) async {
    final result = await _auth.openUsUsTransfers(context);
    _handleWebViewResult(context, result, 'US-US Transfers');
  }

  /// Open wallet
  Future<void> openWallet(BuildContext context) async {
    final result = await _auth.openWallet(context);
    _handleWebViewResult(context, result, 'Wallet');
  }

  /// Open wallet withdrawal
  Future<void> openWalletWithdrawal(BuildContext context) async {
    final result = await _auth.openWalletWithdrawal(context);
    _handleWebViewResult(context, result, 'Wallet Withdrawal');
  }

  /// Open family transfers
  Future<void> openFamilyTransfers(BuildContext context) async {
    final result = await _auth.openFamilyTransfers(context);
    _handleWebViewResult(context, result, 'Family Transfers');
  }

  /// Pay with Remit2Any
  Future<void> payWithRemit2Any(BuildContext context, String sessionId) async {
    try {
      final result = await _auth.payWithRemit2Any(context, sessionId: sessionId);
      
      if (result != null) {
        if (result['event'] == 'onPaymentComplete') {
          print('βœ… Payment completed: ${result['data']}');
          // Handle successful payment
        } else if (result['event'] == 'onPaymentCancelled') {
          print('❌ Payment cancelled: ${result['data']}');
          // Handle cancelled payment
        } else if (result['closed'] == true) {
          print('πŸ“± Payment webview closed by user');
          // Handle manual close
        }
      }
    } catch (e) {
      print('❌ Payment error: $e');
    }
  }

  /// Handle webview results and check for logout
  void _handleWebViewResult(BuildContext context, Map<String, dynamic>? result, String pageName) {
    if (result != null) {
      print('$pageName result: $result');
      
      // Check for logout status
      if (result['loggedOut'] == true) {
        print('🚨 User was logged out from $pageName');
        _handleLogout(context);
      } else if (result['closed'] == true) {
        print('πŸ“± User closed $pageName webview');
      }
    } else {
      print('$pageName: No result returned');
    }
  }

  /// Handle logout from webview
  Future<void> _handleLogout(BuildContext context) async {
    print('πŸ”„ User logged out from webview, initiating re-authentication...');
    
    // NOTE: signOut(context) is ALREADY called internally by the library
    // when loggedOut: true is detected. Just handle re-authentication.
    
    // Trigger sign-in process
    final accessToken = await _auth.getAccessTokenWithAuth(context);
    
    if (accessToken != null) {
      print('βœ… Re-authentication successful');
    } else {
      print('❌ Re-authentication cancelled by user');
    }
  }
}

6. 🚨 Critical: Handle WebView Logout

Important: When any webview returns loggedOut: true, the library automatically calls signOut(context) internally, but you still need to handle re-authentication:

class WebViewHandler {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  Future<void> openAnyWebView(BuildContext context) async {
    // Example: Opening transfer page
    final result = await _auth.openTransfer(context, usdAmount: 100.0);
    
    // CRITICAL: Always check for logout
    if (result != null && result['loggedOut'] == true) {
      print('🚨 CRITICAL: User was logged out from webview!');
      
      // NOTE: signOut(context) is ALREADY called internally by the library
      // You only need to handle re-authentication
      await _handleReAuthentication(context);
    }
  }

  Future<void> _handleReAuthentication(BuildContext context) async {
    print('πŸ”„ Re-authenticating after webview logout...');
    
    try {
      // The library already cleared tokens, just re-authenticate
      final accessToken = await _auth.getAccessTokenWithAuth(context);
      
      if (accessToken != null) {
        print('βœ… Re-authentication successful after webview logout');
        // Continue with your app flow
      } else {
        print('❌ Re-authentication cancelled by user');
        // Handle failed re-authentication (maybe redirect to login screen)
      }
    } catch (e) {
      print('❌ Re-authentication error: $e');
    }
  }
}

What Happens Internally When loggedOut: true

The library automatically performs these cleanup steps:

  1. Calls signOut(context) which:
    • Clears all stored tokens (TokenStorage.clearTokens())
    • Cancels token refresh timers
    • Opens logout WebView to clear Cognito session cookies
  2. Logs the event with debug information
  3. Returns the result with loggedOut: true

You only need to handle the re-authentication part!

7. πŸ’³ Pay with Remit2Any

The payWithRemit2Any() method opens a payment flow using a payment session ID from the Remit2Any Pay API. This method handles payment events and returns event data when the webview closes.

class PaymentService {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Process payment with Remit2Any
  Future<void> processPayment(BuildContext context, String sessionId) async {
    try {
      final result = await _auth.payWithRemit2Any(context, sessionId: sessionId);
      
      if (result != null) {
        // Handle payment completion
        if (result['event'] == 'onPaymentComplete') {
          final paymentData = result['data'];
          print('βœ… Payment completed successfully');
          print('Payment data: $paymentData');
          // Update UI, show success message, etc.
          _handlePaymentSuccess(paymentData);
        }
        // Handle payment cancellation
        else if (result['event'] == 'onPaymentCancelled') {
          final cancelData = result['data'];
          print('❌ Payment was cancelled');
          print('Cancel data: $cancelData');
          // Update UI, show cancellation message, etc.
          _handlePaymentCancellation(cancelData);
        }
        // Handle manual webview close
        else if (result['closed'] == true) {
          print('πŸ“± Payment webview was closed by user');
          // Handle manual close (user might want to retry)
          _handlePaymentClose();
        }
      } else {
        print('⚠️ No result returned from payment flow');
      }
    } catch (e) {
      print('❌ Payment error: $e');
      // Handle error
      _handlePaymentError(e);
    }
  }

  void _handlePaymentSuccess(Map<String, dynamic> data) {
    // Implement your success handling logic
    // e.g., update order status, show success dialog, etc.
  }

  void _handlePaymentCancellation(Map<String, dynamic> data) {
    // Implement your cancellation handling logic
    // e.g., allow retry, show cancellation message, etc.
  }

  void _handlePaymentClose() {
    // Implement your close handling logic
    // e.g., ask user if they want to retry payment
  }

  void _handlePaymentError(dynamic error) {
    // Implement your error handling logic
    // e.g., show error dialog, log error, etc.
  }
}

Payment Event Types:

  • onPaymentComplete - Triggered when user lands on success/failure page after payment
  • onPaymentCancelled - Triggered when user clicks "Back to Payment" on error page
  • closed: true - Returned when user manually closes the webview

Payment Flow:

  1. Get a payment session ID from Remit2Any Pay API
  2. Call payWithRemit2Any() with the session ID
  3. WebView opens with the payment page
  4. User completes or cancels payment
  5. WebView closes and returns event data
  6. Handle the returned event data in your app

Example Integration:

// Step 1: Get session ID from your backend/API
final sessionId = await yourApiService.createPaymentSession(amount: 100.0);

// Step 2: Open payment flow
final paymentResult = await _auth.payWithRemit2Any(context, sessionId: sessionId);

// Step 3: Handle result
if (paymentResult?['event'] == 'onPaymentComplete') {
  // Payment successful - update your order status
  await yourApiService.confirmPayment(sessionId);
}

8. πŸ”„ Complete Authentication Flow Example

Here's a complete example showing the recommended authentication pattern:

class CompleteAuthFlow {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Complete authentication and API flow
  Future<void> performAuthenticatedAction(BuildContext context, {String? email, String? referralCode}) async {
    try {
      // Step 1: Get access token (try existing first)
      String? accessToken = await _auth.getAccessToken();
      
      // Step 2: If null, authenticate
      if (accessToken == null) {
        print('πŸ”‘ No access token found, authenticating...');
        accessToken = await _auth.getAccessTokenWithAuth(context, email: email, referralCode: referralCode);
        
        if (accessToken == null) {
          print('❌ Authentication cancelled');
          return;
        }
      }
      
      // Step 3: Use access token for API calls
      await _makeApiCall(accessToken);
      
      // Step 4: Open webview and handle potential logout
      await _openWebViewWithLogoutHandling(context);
      
    } catch (e) {
      print('❌ Authentication flow error: $e');
    }
  }

  Future<void> _makeApiCall(String accessToken) async {
    print('🌐 Making API call with token...');
    // Your API implementation here
  }

  Future<void> _openWebViewWithLogoutHandling(BuildContext context) async {
    final result = await _auth.openWallet(context);
    
    // Handle webview result
    if (result != null) {
      if (result['loggedOut'] == true) {
        print('🚨 Webview logout detected, re-authenticating...');
        
        // NOTE: Library already called signOut(context) internally
        // Just handle re-authentication
        final storedEmail = await _auth.getStoredEmail();
        final newToken = await _auth.getAccessTokenWithAuth(context, email: storedEmail);
        
        if (newToken != null) {
          print('βœ… Re-authentication successful');
        }
      }
    }
  }
}

9. πŸ› οΈ Additional Utility Methods

class AuthUtilities {
  final Remit2AnyAuth _auth = Remit2AnyAuth();

  /// Get stored user information
  Future<Map<String, String?>> getStoredUserData() async {
    final email = await _auth.getStoredEmail();
    final userId = await _auth.getStoredUserId();
    
    return {
      'email': email,
      'userId': userId,
    };
  }

  /// Check US KYC completion status
  Future<bool> checkUsKycStatus() async {
    try {
      return await _auth.isUsKycCompleted();
    } catch (e) {
      print('Error checking US KYC status: $e');
      return false;
    }
  }

  /// Check India KYC completion status
  Future<bool> checkIndiaKycStatus() async {
    try {
      return await _auth.isIndiaKycCompleted();
    } catch (e) {
      print('Error checking India KYC status: $e');
      return false;
    }
  }

  /// Complete sign out
  Future<void> signOut(BuildContext context) async {
    try {
      await _auth.signOut(context);
      print('βœ… Successfully signed out');
    } catch (e) {
      print('❌ Sign out error: $e');
    }
  }
}

10. πŸ“‹ Quick Reference

Essential Methods Summary

// 1. Setup (in main.dart)
Remit2AnyEnvironmentConfig.setEnvironment(Environment.dev);
Remit2AnyEnvironmentConfig.setUserAgentSuffix('yourApp');

// 2. Sign In (with optional email, referral code, and force WebView)
final accessToken = await auth.getAccessTokenWithAuth(context, email: "user@example.com", referralCode: "REF123", forceWebView: false);

// 3. Get Access Token (check for null!)
final token = await auth.getAccessToken();
if (token == null) {
  // Must sign in again (with optional email, referral code, and force WebView)
  await auth.getAccessTokenWithAuth(context, email: "user@example.com", referralCode: "REF123", forceWebView: false);
}

// 4. Open WebViews (check for logout!)
final result = await auth.openTransfer(context);
if (result?['loggedOut'] == true) {
  // Must sign in again (with optional email, referral code, and force WebView)
  await auth.getAccessTokenWithAuth(context, email: "user@example.com", referralCode: "REF123", forceWebView: false);
}

// 5. Pay with Remit2Any
final paymentResult = await auth.payWithRemit2Any(context, sessionId: "session_123");
if (paymentResult?['event'] == 'onPaymentComplete') {
  // Handle successful payment
}

// 6. Sign Out (with optional redirect to login)
await auth.signOut(context, redirectToLogin: false);

Available WebView Methods

All methods return Future<Map<String, dynamic>?> and should be checked for loggedOut: true:

  • openFullSite(context, {tenant}) - Complete website
  • openTransfer(context, {usdAmount}) - Transfer page
  • openWalletDeposit(context) - Wallet deposit
  • openWalletWithdrawal(context) - Wallet withdrawal
  • openTransactions(context) - Transaction history
  • openUsKyc(context) - US KYC documents
  • openIndianKyc(context) - Indian KYC documents
  • openBankAccounts(context) - Bank account management
  • openUsUsTransfers(context) - US to US transfers
  • openWallet(context) - Wallet overview
  • openFamilyTransfers(context) - Family transfers page
  • payWithRemit2Any(context, {required sessionId}) - Pay with Remit2Any flow

🎯 Key Features Explained

Enhanced Keyboard Handling

The package includes advanced keyboard handling that automatically:

  • βœ… Prevents input fields from being hidden behind the keyboard on iOS
  • βœ… Maintains Android's existing smooth keyboard behavior
  • βœ… Automatically scrolls focused input fields into view
  • βœ… Handles viewport adjustments for optimal user experience

Intelligent Permission Management

Permissions are requested dynamically based on URL patterns:

  • πŸŽ₯ Camera & Microphone: Automatically requested for URLs containing uskyc or kyc-documents
  • πŸ”’ Secure Domains: Only trusted Remit2Any domains are allowed
  • 🌐 External URLs: WhatsApp links and other external URLs open in system browser

Smart WebView Navigation

  • πŸ“± Platform Optimization: Different behaviors optimized for iOS and Android
  • πŸ”„ User Agent Management: Dynamic user agent switching for different services (Google OAuth, etc.)
  • πŸ“ File Downloads: Native file download handling with system integration
  • πŸ” Security: SSL certificate validation with custom handling

πŸ› οΈ Advanced Configuration

Environment Variables

// Check current environment
if (Remit2AnyEnvironmentConfig.isDev) {
  print('Development mode');
} else {
  print('Production mode');
}

// Get environment-specific URLs
print('Auth URL: ${Remit2AnyEnvironmentConfig.authUrl}');
print('Base URL: ${Remit2AnyEnvironmentConfig.baseUrl}');
print('Payment Base URL: ${Remit2AnyEnvironmentConfig.paymentBaseUrl}');
print('User Agent Suffix: ${Remit2AnyEnvironmentConfig.userAgentSuffix}');

// Build payment URL
final paymentUrl = Remit2AnyEnvironmentConfig.buildPaymentUrl('session_123');
print('Payment URL: $paymentUrl');

## πŸ”§ Dependencies

This package depends on:
- **flutter_inappwebview**: ^6.0.0 - Advanced WebView functionality
- **flutter_secure_storage**: ^9.0.0 - Secure token storage
- **http**: ^1.0.0 - HTTP requests for API calls
- **permission_handler**: ^11.0.0 - Runtime permission management
- **url_launcher**: ^6.1.4 - External URL handling
- **path_provider**: ^2.1.1 - File system access for downloads

## πŸ› Troubleshooting

### Common Issues

πŸ“š API Reference

Core Classes

Remit2AnyAuth

Main authentication service class.

Methods:

  • signIn(BuildContext context, {String? email, String? referralCode, bool forceWebView = false}) β†’ Future<Map<String, dynamic>?>
  • signOut(BuildContext context, {bool redirectToLogin = false}) β†’ Future<void>
  • getAccessToken() β†’ Future<String?>
  • getAccessTokenWithAuth(BuildContext context, {String? email, String? referralCode, bool forceWebView = false}) β†’ Future<String?>
  • openFullSite(BuildContext context, {String? tenant}) β†’ Future<Map<String, dynamic>?>
  • openUsKyc(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • openTransfer(BuildContext context, {double? usdAmount}) β†’ Future<Map<String, dynamic>?>
  • openWalletDeposit(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • openWalletWithdrawal(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • openTransactions(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • openFamilyTransfers(BuildContext context) β†’ Future<Map<String, dynamic>?>
  • payWithRemit2Any(BuildContext context, {required String sessionId}) β†’ Future<Map<String, dynamic>?>
  • isUsKycCompleted() β†’ Future<bool>
  • isIndiaKycCompleted() β†’ Future<bool>
  • getStoredEmail() β†’ Future<String?>
  • getStoredUserId() β†’ Future<String?>

Remit2AnyEnvironmentConfig

Environment configuration management.

Methods:

  • setEnvironment(Environment environment) β†’ void
  • setUserAgentSuffix(String suffix) β†’ void

Properties:

  • environment β†’ Environment
  • authUrl β†’ String
  • baseUrl β†’ String
  • paymentBaseUrl β†’ String
  • userAgentSuffix β†’ String
  • isDev β†’ bool

Methods:

  • buildPaymentUrl(String sessionId) β†’ String - Builds payment URL with session ID

πŸ”„ Migration Guide

From v0.1.8 to Latest

  • NEW: Added referralCode parameter to signIn() and getAccessTokenWithAuth() for mypursu tenant support
  • NEW: Added payWithRemit2Any() method for payment session handling
  • NEW: Added openFamilyTransfers() method for family transfers page
  • NEW: Added paymentBaseUrl property and buildPaymentUrl() method to Remit2AnyEnvironmentConfig
  • NEW: Added extraAllowedHosts parameter to CommonWebView for additional domain support
  • IMPROVED: Enhanced signup flow with referral code support using /auth/register-widget path

From v0.1.6 to v0.1.8

  • NEW: Added forceWebView parameter to getAccessTokenWithAuth() and signIn()
  • NEW: Added redirectToLogin parameter to signOut()
  • NEW: Memory caching for token storage (faster performance)
  • NEW: Centralized authentication handler to eliminate code duplication
  • IMPROVED: Enhanced logout flow with automatic redirect to login
  • IMPROVED: Better error handling and logging

From v0.1.0 to v0.1.6

  • Enhanced keyboard handling for iOS
  • Improved permission management
  • Added WhatsApp URL handling
  • Enhanced WebView components

No breaking changes - update your pubspec.yaml version and run flutter pub get.

Development Setup

git clone https://github.com/your-repo/remit2any_auth.git
cd remit2any_auth
flutter pub get
cd example
flutter pub get
flutter run

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

Made with ❀️ by the Remit2Any Team