> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monieswitch.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrating Monieswitch with Mobile Apps (React Native & Flutter)

> A comprehensive guide to securely connecting your mobile app to Monieswitch APIs for payments and wallet management.

### Who is this for?

<Info>
  Mobile developers using React Native or Flutter who want to accept payments,
  create payment links, or manage wallets with Monieswitch APIs.
</Info>

## Overview

This guide covers:

* Setting up secure API integration architecture
* Creating a backend proxy for Monieswitch APIs
* Building mobile app clients that communicate with your backend
* Creating payment links and handling responses
* Security best practices for mobile payments
* Error handling and user experience considerations

***

## Prerequisites

<Steps>
  <Step title="Create Monieswitch Account">
    **A Monieswitch account**. Register at [Monieswitch
    Dashboard](https://dashboard.monieswitch.com/register).
  </Step>

  <Step title="Get API Key">**API Key** from your Monieswitch dashboard.</Step>

  <Step title="Set Up Development Environment">
    **React Native** or **Flutter** development environment set up.
  </Step>

  <Step title="Backend Service">
    **A backend service** (Node.js, Python, etc.) to securely handle Monieswitch API calls.
  </Step>
</Steps>

***

## Architecture Overview

```
Mobile App → Your Backend API → Monieswitch APIs
```

<Warning title="Security Architecture">
  Mobile apps should **never** communicate directly with Monieswitch APIs. Always use a secure backend to proxy requests and protect your API keys.
</Warning>

***

## 1. Backend Setup (Required)

First, set up your backend to handle Monieswitch API calls:

### Environment Configuration

Add these to your backend `.env` file:

```env theme={"system"}
MONIESWITCH_BASE_URL=https://nini.monieswitch.com
MONIESWITCH_API_KEY=your_api_key_here
PORT=3000
```

### Backend Implementation

<Tabs>
  <Tab title="Node.js/Express">
    ```javascript server.js theme={"system"}
    require('dotenv').config();
    const express = require('express');
    const axios = require('axios');
    const app = express();

    app.use(express.json());

    const BASE_URL = process.env.MONIESWITCH_BASE_URL;
    const API_KEY = process.env.MONIESWITCH_API_KEY;

    // Get merchant details
    app.get('/api/monieswitch/merchant', async (req, res) => {
      try {
        const response = await axios.get(`${BASE_URL}/merchant`, {
          headers: {
            'Authorization': `Bearer ${API_KEY}`,
            'Content-Type': 'application/json'
          }
        });
        res.json(response.data);
      } catch (error) {
        console.error('Monieswitch API Error:', error.response?.data);
        res.status(error.response?.status || 500).json({
          error: 'Failed to fetch merchant details'
        });
      }
    });

    // Create payment link
    app.post('/api/monieswitch/payment-links', async (req, res) => {
      try {
        const { amount, currency, description, email } = req.body;

        // Validate required fields
        if (!amount || !currency || !email) {
          return res.status(400).json({
            error: 'Missing required fields: amount, currency, email'
          });
        }

        const payload = {
          amount: parseInt(amount),
          currency,
          description: description || 'Mobile payment',
          email
        };

        const response = await axios.post(`${BASE_URL}/payment-links`, payload, {
          headers: {
            'Authorization': `Bearer ${API_KEY}`,
            'Content-Type': 'application/json'
          }
        });

        res.json(response.data);
      } catch (error) {
        console.error('Payment Link Error:', error.response?.data);
        res.status(error.response?.status || 500).json({
          error: 'Failed to create payment link'
        });
      }
    });

    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
      console.log(`Backend server running on port ${PORT}`);
    });
    ```
  </Tab>
</Tabs>

***

## 2. Mobile App Setup

### Install HTTP Request Libraries

<Tabs>
  <Tab title="React Native">
    ```bash theme={"system"}
    npm install axios
    # For secure storage (optional)
    npm install @react-native-async-storage/async-storage
    ```
  </Tab>

  <Tab title="Flutter">
    ```yaml pubspec.yaml theme={"system"}
    dependencies:
      http: ^1.1.0
      # For secure storage (optional)
      flutter_secure_storage: ^9.0.0
    ```

    Then run:

    ```bash theme={"system"}
    flutter pub get
    ```
  </Tab>
</Tabs>

### Configure API Client

<Tabs>
  <Tab title="React Native">
    Create an API service file:

    ```javascript services/api.js theme={"system"}
    import axios from 'axios';

    // Configure your backend URL
    const BACKEND_BASE_URL = 'https://your-backend.com'; // Replace with your backend URL
    // For local development: 'http://localhost:3000'

    const apiClient = axios.create({
      baseURL: BACKEND_BASE_URL,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    // Add request interceptor for logging
    apiClient.interceptors.request.use(
      (config) => {
        console.log('API Request:', config.method?.toUpperCase(), config.url);
        return config;
      },
      (error) => Promise.reject(error)
    );

    // Add response interceptor for error handling
    apiClient.interceptors.response.use(
      (response) => response,
      (error) => {
        console.error('API Error:', error.response?.data || error.message);
        return Promise.reject(error);
      }
    );

    export const monieswitchAPI = {
      // Get merchant details
      getMerchantDetails: async () => {
        try {
          const response = await apiClient.get('/api/monieswitch/merchant');
          return response.data;
        } catch (error) {
          throw new Error(error.response?.data?.error || 'Failed to fetch merchant details');
        }
      },

      // Create payment link
      createPaymentLink: async (paymentData) => {
        try {
          const response = await apiClient.post('/api/monieswitch/payment-links', paymentData);
          return response.data;
        } catch (error) {
          throw new Error(error.response?.data?.error || 'Failed to create payment link');
        }
      },
    };
    ```
  </Tab>

  <Tab title="Flutter">
    Create an API service class:

    ```dart lib/services/monieswitch_service.dart theme={"system"}
    import 'package:http/http.dart' as http;
    import 'dart:convert';

    class MonieswitchService {
      // Configure your backend URL
      static const String _baseUrl = 'https://your-backend.com'; // Replace with your backend URL
      // For local development: 'http://localhost:3000'

      static const Duration _timeout = Duration(seconds: 10);

      static Future<Map<String, dynamic>> _makeRequest(
        String method,
        String endpoint,
        {Map<String, dynamic>? body}
      ) async {
        final uri = Uri.parse('$_baseUrl$endpoint');
        final headers = {'Content-Type': 'application/json'};

        print('API Request: $method $endpoint');

        http.Response response;

        try {
          switch (method.toUpperCase()) {
            case 'GET':
              response = await http.get(uri, headers: headers).timeout(_timeout);
              break;
            case 'POST':
              response = await http.post(
                uri,
                headers: headers,
                body: body != null ? json.encode(body) : null,
              ).timeout(_timeout);
              break;
            default:
              throw Exception('Unsupported HTTP method: $method');
          }
        } catch (e) {
          throw Exception('Network error: ${e.toString()}');
        }

        final responseData = json.decode(response.body);

        if (response.statusCode >= 200 && response.statusCode < 300) {
          return responseData;
        } else {
          final errorMessage = responseData['error'] ?? 'Unknown error occurred';
          throw Exception(errorMessage);
        }
      }

      // Get merchant details
      static Future<Map<String, dynamic>> getMerchantDetails() async {
        return await _makeRequest('GET', '/api/monieswitch/merchant');
      }

      // Create payment link
      static Future<Map<String, dynamic>> createPaymentLink({
        required int amount,
        required String currency,
        required String email,
        String? description,
      }) async {
        final body = {
          'amount': amount,
          'currency': currency,
          'email': email,
          if (description != null) 'description': description,
        };

        return await _makeRequest('POST', '/api/monieswitch/payment-links', body: body);
      }
    }
    ```
  </Tab>
</Tabs>

***

## 3. Using the API in Your App

### Fetching Merchant Details

<Tabs>
  <Tab title="React Native">
    ```javascript components/MerchantInfo.js theme={"system"}
    import React, { useState, useEffect } from 'react';
    import { View, Text, ActivityIndicator, Alert } from 'react-native';
    import { monieswitchAPI } from '../services/api';

    const MerchantInfo = () => {
      const [merchantData, setMerchantData] = useState(null);
      const [loading, setLoading] = useState(true);

      useEffect(() => {
        fetchMerchantDetails();
      }, []);

      const fetchMerchantDetails = async () => {
        try {
          setLoading(true);
          const data = await monieswitchAPI.getMerchantDetails();
          setMerchantData(data);
        } catch (error) {
          Alert.alert('Error', error.message);
        } finally {
          setLoading(false);
        }
      };

      if (loading) {
        return (
          <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
            <ActivityIndicator size="large" />
          </View>
        );
      }

      return (
        <View>
          <Text>Merchant: {merchantData?.name}</Text>
          <Text>Email: {merchantData?.email}</Text>
        </View>
      );
    };

    export default MerchantInfo;
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart lib/widgets/merchant_info.dart theme={"system"}
    import 'package:flutter/material.dart';
    import '../services/monieswitch_service.dart';

    class MerchantInfo extends StatefulWidget {
      @override
      _MerchantInfoState createState() => _MerchantInfoState();
    }

    class _MerchantInfoState extends State<MerchantInfo> {
      Map<String, dynamic>? merchantData;
      bool isLoading = true;
      String? errorMessage;

      @override
      void initState() {
        super.initState();
        fetchMerchantDetails();
      }

      Future<void> fetchMerchantDetails() async {
        try {
          setState(() {
            isLoading = true;
            errorMessage = null;
          });

          final data = await MonieswitchService.getMerchantDetails();

          setState(() {
            merchantData = data;
            isLoading = false;
          });
        } catch (e) {
          setState(() {
            errorMessage = e.toString();
            isLoading = false;
          });
        }
      }

      @override
      Widget build(BuildContext context) {
        if (isLoading) {
          return Center(child: CircularProgressIndicator());
        }

        if (errorMessage != null) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Error: $errorMessage', style: TextStyle(color: Colors.red)),
                ElevatedButton(
                  onPressed: fetchMerchantDetails,
                  child: Text('Retry'),
                ),
              ],
            ),
          );
        }

        return Column(
          children: [
            Text('Merchant: ${merchantData?['name'] ?? 'N/A'}'),
            Text('Email: ${merchantData?['email'] ?? 'N/A'}'),
          ],
        );
      }
    }
    ```
  </Tab>
</Tabs>

### Creating Payment Links

<Tabs>
  <Tab title="React Native">
    ```javascript components/PaymentLinkForm.js theme={"system"}
    import React, { useState } from 'react';
    import { View, Text, TextInput, TouchableOpacity, Alert, ActivityIndicator } from 'react-native';
    import { monieswitchAPI } from '../services/api';

    const PaymentLinkForm = () => {
      const [formData, setFormData] = useState({
        amount: '',
        currency: 'NGN',
        description: '',
        email: '',
      });
      const [loading, setLoading] = useState(false);

      const handleInputChange = (field, value) => {
        setFormData(prev => ({ ...prev, [field]: value }));
      };

      const createPaymentLink = async () => {
        if (!formData.amount || !formData.email) {
          Alert.alert('Error', 'Please fill in amount and email');
          return;
        }

        try {
          setLoading(true);
          const paymentData = {
            amount: parseInt(formData.amount),
            currency: formData.currency,
            description: formData.description || 'Mobile payment',
            email: formData.email,
          };

          const response = await monieswitchAPI.createPaymentLink(paymentData);

          Alert.alert(
            'Success',
            `Payment link created! URL: ${response.payment_url}`,
            [{ text: 'OK' }]
          );

          // Reset form
          setFormData({ amount: '', currency: 'NGN', description: '', email: '' });
        } catch (error) {
          Alert.alert('Error', error.message);
        } finally {
          setLoading(false);
        }
      };

      return (
        <View style={{ padding: 20 }}>
          <Text style={{ fontSize: 18, marginBottom: 20 }}>Create Payment Link</Text>

          <TextInput
            placeholder="Amount (e.g., 5000)"
            value={formData.amount}
            onChangeText={(value) => handleInputChange('amount', value)}
            keyboardType="numeric"
            style={{ borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 }}
          />

          <TextInput
            placeholder="Currency (e.g., NGN)"
            value={formData.currency}
            onChangeText={(value) => handleInputChange('currency', value)}
            style={{ borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 }}
          />

          <TextInput
            placeholder="Description (optional)"
            value={formData.description}
            onChangeText={(value) => handleInputChange('description', value)}
            style={{ borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 }}
          />

          <TextInput
            placeholder="Customer Email"
            value={formData.email}
            onChangeText={(value) => handleInputChange('email', value)}
            keyboardType="email-address"
            style={{ borderWidth: 1, padding: 10, marginBottom: 20, borderRadius: 5 }}
          />

          <TouchableOpacity
            onPress={createPaymentLink}
            disabled={loading}
            style={{
              backgroundColor: loading ? '#ccc' : '#007AFF',
              padding: 15,
              borderRadius: 5,
              alignItems: 'center'
            }}
          >
            {loading ? (
              <ActivityIndicator color="white" />
            ) : (
              <Text style={{ color: 'white', fontSize: 16 }}>Create Payment Link</Text>
            )}
          </TouchableOpacity>
        </View>
      );
    };

    export default PaymentLinkForm;
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart lib/widgets/payment_link_form.dart theme={"system"}
    import 'package:flutter/material.dart';
    import '../services/monieswitch_service.dart';

    class PaymentLinkForm extends StatefulWidget {
      @override
      _PaymentLinkFormState createState() => _PaymentLinkFormState();
    }

    class _PaymentLinkFormState extends State<PaymentLinkForm> {
      final _formKey = GlobalKey<FormState>();
      final _amountController = TextEditingController();
      final _currencyController = TextEditingController(text: 'NGN');
      final _descriptionController = TextEditingController();
      final _emailController = TextEditingController();
      bool _isLoading = false;

      @override
      void dispose() {
        _amountController.dispose();
        _currencyController.dispose();
        _descriptionController.dispose();
        _emailController.dispose();
        super.dispose();
      }

      Future<void> _createPaymentLink() async {
        if (!_formKey.currentState!.validate()) return;

        setState(() => _isLoading = true);

        try {
          final response = await MonieswitchService.createPaymentLink(
            amount: int.parse(_amountController.text),
            currency: _currencyController.text,
            email: _emailController.text,
            description: _descriptionController.text.isEmpty
                ? null
                : _descriptionController.text,
          );

          if (mounted) {
            showDialog(
              context: context,
              builder: (context) => AlertDialog(
                title: Text('Success'),
                content: Text('Payment link created!\n\nURL: ${response['payment_url']}'),
                actions: [
                  TextButton(
                    onPressed: () => Navigator.of(context).pop(),
                    child: Text('OK'),
                  ),
                ],
              ),
            );

            // Reset form
            _amountController.clear();
            _currencyController.text = 'NGN';
            _descriptionController.clear();
            _emailController.clear();
          }
        } catch (e) {
          if (mounted) {
            showDialog(
              context: context,
              builder: (context) => AlertDialog(
                title: Text('Error'),
                content: Text(e.toString().replaceFirst('Exception: ', '')),
                actions: [
                  TextButton(
                    onPressed: () => Navigator.of(context).pop(),
                    child: Text('OK'),
                  ),
                ],
              ),
            );
          }
        } finally {
          if (mounted) {
            setState(() => _isLoading = false);
          }
        }
      }

      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: EdgeInsets.all(16.0),
          child: Form(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Text(
                  'Create Payment Link',
                  style: Theme.of(context).textTheme.headlineSmall,
                ),
                SizedBox(height: 20),

                TextFormField(
                  controller: _amountController,
                  decoration: InputDecoration(
                    labelText: 'Amount',
                    hintText: 'e.g., 5000',
                    border: OutlineInputBorder(),
                  ),
                  keyboardType: TextInputType.number,
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please enter an amount';
                    }
                    if (int.tryParse(value) == null) {
                      return 'Please enter a valid number';
                    }
                    return null;
                  },
                ),
                SizedBox(height: 16),

                TextFormField(
                  controller: _currencyController,
                  decoration: InputDecoration(
                    labelText: 'Currency',
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please enter a currency';
                    }
                    return null;
                  },
                ),
                SizedBox(height: 16),

                TextFormField(
                  controller: _descriptionController,
                  decoration: InputDecoration(
                    labelText: 'Description (optional)',
                    border: OutlineInputBorder(),
                  ),
                ),
                SizedBox(height: 16),

                TextFormField(
                  controller: _emailController,
                  decoration: InputDecoration(
                    labelText: 'Customer Email',
                    border: OutlineInputBorder(),
                  ),
                  keyboardType: TextInputType.emailAddress,
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please enter an email';
                    }
                    if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
                      return 'Please enter a valid email';
                    }
                    return null;
                  },
                ),
                SizedBox(height: 24),

                ElevatedButton(
                  onPressed: _isLoading ? null : _createPaymentLink,
                  child: _isLoading
                      ? SizedBox(
                          height: 20,
                          width: 20,
                          child: CircularProgressIndicator(strokeWidth: 2),
                        )
                      : Text('Create Payment Link'),
                  style: ElevatedButton.styleFrom(
                    padding: EdgeInsets.symmetric(vertical: 16),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    ```
  </Tab>
</Tabs>

***

## 4. Error Handling Best Practices

### Common Error Scenarios

1. **Network connectivity issues**
2. **Invalid API responses**
3. **Authentication failures**
4. **Validation errors**
5. **Server timeouts**

### Implementation Examples

<Tabs>
  <Tab title="React Native">
    ```javascript utils/errorHandler.js theme={"system"}
    export const handleAPIError = (error, showAlert = true) => {
      let errorMessage = 'An unexpected error occurred';

      if (error.response) {
        // Server responded with error status
        errorMessage = error.response.data?.error || `Server error: ${error.response.status}`;
      } else if (error.request) {
        // Request was made but no response received
        errorMessage = 'Network error. Please check your connection.';
      } else {
        // Something else happened
        errorMessage = error.message || errorMessage;
      }

      console.error('API Error:', errorMessage);

      if (showAlert) {
        Alert.alert('Error', errorMessage);
      }

      return errorMessage;
    };
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart lib/utils/error_handler.dart theme={"system"}
    import 'package:flutter/material.dart';

    class ErrorHandler {
      static void handleError(BuildContext context, dynamic error) {
        String errorMessage = 'An unexpected error occurred';

        if (error is Exception) {
          String exceptionString = error.toString();
          if (exceptionString.startsWith('Exception: ')) {
            errorMessage = exceptionString.substring(11);
          } else {
            errorMessage = exceptionString;
          }
        }

        print('Error: $errorMessage');

        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text('Error'),
            content: Text(errorMessage),
            actions: [
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: Text('OK'),
              ),
            ],
          ),
        );
      }
    }
    ```
  </Tab>
</Tabs>

***

## 5. Security Best Practices

<Warning title="Critical Security Requirements">
  * **Never store API keys in mobile app code**
  * **Always use HTTPS for all communications**
  * **Implement proper input validation**
  * **Use certificate pinning for production apps**
  * **Implement request signing for sensitive operations**
</Warning>

### Additional Security Measures

1. **Input Validation**: Always validate user input before sending to your backend
2. **Rate Limiting**: Implement rate limiting on your backend API endpoints
3. **Authentication**: Use proper authentication mechanisms between your app and backend
4. **Logging**: Log API requests and responses for debugging (exclude sensitive data)
5. **Error Messages**: Don't expose internal system details in error messages

***

## 6. Testing Your Integration

### Backend Testing

```bash theme={"system"}
# Test merchant details endpoint
curl -X GET http://localhost:3000/api/monieswitch/merchant

# Test payment link creation
curl -X POST http://localhost:3000/api/monieswitch/payment-links \
  -H "Content-Type: application/json" \
  -d '{"amount": 5000, "currency": "NGN", "email": "test@example.com"}'
```

***

## Next Steps

* Explore more endpoints in the [API Reference](/api-reference/introduction)
* Implement [webhooks](/features/payments/webhook) on your backend for real-time payment updates

***

## Troubleshooting

### Common Issues

1. **CORS errors**: Ensure your backend properly handles CORS if testing from web
2. **Network timeouts**: Implement proper timeout handling and retry logic
3. **Environment variables not loading**: Double-check your `.env` file location and format
4. **API key authentication failures**: Verify your API key is correct and has proper permissions

### Debug Tips

* Enable detailed logging in both mobile app and backend
* Use network debugging tools (React Native Debugger, Flutter Inspector)
* Test API endpoints independently before integrating with mobile app
* Monitor backend logs for detailed error informati
