npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flixora/airxpay-react-native

v0.0.5

Published

AirXPay Initialization UI Components for React & React Native

Readme

📱 Complete Integration Guide - @flixora/airxpay-react-native

🎯 Understanding the Complete Implementation

Below is a production-ready implementation showing exactly how to integrate the SDK. I'll explain EACH PART in detail so developers understand WHY and HOW to use it.

@flixora/airxpay-react-native 🚀

npm version license downloads

Complete React Native SDK for AirXPay - Merchant onboarding with KYC verification, bank details, and document uploads. Built with Expo and TypeScript.

📦 Installation

npm install @flixora/airxpay-sdk-init-ui
# or
yarn add @flixora/airxpay-sdk-init-ui

Peer Dependencies

Make sure you have these installed:

npm install @react-native-async-storage/async-storage @react-native-community/datetimepicker @react-navigation/native @types/react axios expo-image-picker expo-linear-gradient expo-module-scripts react react-native react-native-country-picker-modal react-native-paper typescript
# or
yarn add @react-native-async-storage/async-storage @react-native-community/datetimepicker @react-navigation/native @types/react axios expo-image-picker expo-linear-gradient expo-module-scripts react react-native react-native-country-picker-modal react-native-paper typescript

🚀 Quick Start


📁 File: MerchantOnboardingScreen.tsx - Complete Code with Explanations

import React, { useEffect, useState } from "react";
import { View, ActivityIndicator, Text, Alert } from "react-native";
import { useNavigation } from "@react-navigation/native";
import {
  MerchantOnboarding,
  useAirXPaySafe,
  tokenService,
  OnboardingCompleteScreen,
  useMerchantOnboarding,
} from "@flixora/airxpay-react-native";

🔍 Imports Explained:

| Import | Purpose | |--------|---------| | MerchantOnboarding | Main 5-step onboarding component | | useAirXPaySafe | Hook to access SDK context (safe version - returns null if not in provider) | | tokenService | Utility to save/retrieve JWT tokens securely | | OnboardingCompleteScreen | Success screen after onboarding | | useMerchantOnboarding | Hook for merchant operations (create, fetch status) |


🏗️ Component Setup

export default function MerchantOnboardingScreen() {
  const airxpay = useAirXPaySafe();
  const navigation = useNavigation();
  const { createMerchant, loading: merchantLoading } = useMerchantOnboarding();

📌 State Variables Explained:

  const [isReady, setIsReady] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [fullFormData, setFullFormData] = useState<Record<string, any>>({});
  const [hasSavedToken, setHasSavedToken] = useState(false);
  const [merchantData, setMerchantData] = useState<any>(null);
  const [loadingStatus, setLoadingStatus] = useState(false);
  const [tokenInvalid, setTokenInvalid] = useState(false);

| State | Purpose | |-------|---------| | isReady | SDK initialized and ready to use | | isSubmitting | Form submission in progress | | fullFormData | Accumulates data from all steps | | hasSavedToken | User already has valid token | | merchantData | Fetched merchant details from API | | loadingStatus | Fetching merchant status | | tokenInvalid | Token expired/invalid - show form again |


🔐 Token Management

🚫 Invalid Token Handler

  const handleInvalidToken = async () => {
    console.log("🔴 Token invalid, clearing from device");
    await tokenService.clearToken();        // Remove from storage
    setHasSavedToken(false);                // Reset state
    setMerchantData(null);                  // Clear merchant data
    setTokenInvalid(true);                   // Trigger form display

    Alert.alert(
      "Session Expired",
      "Your session has expired. Please complete the form again.",
      [{ text: "OK" }],
    );
  };

Why this is important:

  • Automatically handles expired tokens
  • Shows user-friendly alert
  • Resets to onboarding form
  • Prevents getting stuck on error screen

📡 Fetching Merchant Data

🔄 Status API Call with Token Verification

  const fetchMerchantData = async (token: string) => {
    console.log(
      "🔄 Starting fetchMerchantData with token:",
      token ? "Token exists" : "No token",
    );

    try {
      setLoadingStatus(true);
      console.log(
        "🌐 Calling status API endpoint: http://172.20.10.12:5000/api/payout-portal/wallet-setup/sellers/status",
      );

      const response = await fetch(
        "http://172.20.10.12:5000/api/payout-portal/wallet-setup/sellers/status",
        {
          headers: { Authorization: `Bearer ${token}` },
        },
      );

⚠️ Token Validation Logic

      if (
        response.status === 404 ||
        response.status === 401 ||
        response.status === 403
      ) {
        console.log(
          `⚠️ Status API returned ${response.status} - Token invalid`,
        );
        await handleInvalidToken();  // 🔥 Token invalid - clear and restart
        return null;
      }

HTTP Status Handling:

  • 401 - Unauthorized (token expired)
  • 403 - Forbidden (invalid token)
  • 404 - Not found (merchant doesn't exist)
  • Any other error - handle gracefully

Successful Response Handling

      if (response.ok) {
        const data = await response.json();
        console.log("✅ SUCCESS - Merchant data fetched successfully!");
        
        // 🔥 IMPORTANT: Extract nested data structure
        // API returns { success: true, data: { ...merchantFields } }
        const extractedData = data.data || data;

        console.log(
          "📊 Extracted merchant data:",
          JSON.stringify(extractedData, null, 2),
        );

        // ✅ Verify all expected fields exist
        const expectedFields = [
          "status",
          "kycStatus",
          "dob",
          "merchantDID",
          "walletId",
          "merchantName",
          "merchantEmail",
          "merchantPhone",
        ];
        expectedFields.forEach((field) => {
          console.log(
            `   - ${field}:`,
            extractedData[field]
              ? `Present (${extractedData[field]})`
              : "❌ Missing",
          );
        });

        setMerchantData(extractedData);  // Save to state
        setHasSavedToken(true);           // Mark as authenticated
        return extractedData;
      }

Key Insight: The API returns nested data structure. Always extract properly!


⏳ SDK Initialization & Token Check

  useEffect(() => {
    console.log(
      "🔄 useEffect triggered, airxpay:",
      airxpay ? "exists" : "null",
    );

    if (airxpay) {
      const checkReady = async () => {
        console.log("🔍 Checking SDK readiness...");
        
        // ⏱️ Wait for SDK to finish loading
        let tries = 0;
        while (airxpay.loading && tries < 20) {
          console.log(`⏳ Waiting for SDK to load... attempt ${tries + 1}/20`);
          await new Promise((res) => setTimeout(res, 100));
          tries++;
        }

        console.log("✅ SDK ready check complete, isReady set to true");
        setIsReady(true);

        // 🔑 Check for existing token
        console.log("🔑 Checking for saved token...");
        const token = await tokenService.getToken();
        console.log("🔑 Token found?", token ? "Yes" : "No");

        if (token) {
          console.log(
            "🔑 Token exists (first 10 chars):",
            token.substring(0, 10) + "...",
          );
          console.log("🔑 Verifying token with status API...");
          await fetchMerchantData(token);  // Verify token is valid
        } else {
          console.log("🔑 No token found, showing onboarding form");
          setHasSavedToken(false);
        }
      };
      checkReady();
    }
  }, [airxpay]); // Runs when airxpay context changes

🔄 Why This Pattern?

  1. Wait for SDK - Don't render until SDK is ready
  2. Check Existing Token - User might already be authenticated
  3. Verify Token - Token might be expired/invalid
  4. Show Appropriate UI - Form or Success screen

📊 State Change Monitoring

  useEffect(() => {
    console.log("🔄 merchantData state changed:");
    console.log(
      "   Current merchantData:",
      merchantData ? JSON.stringify(merchantData, null, 2) : "null",
    );
    console.log("   hasSavedToken:", hasSavedToken);
    console.log("   loadingStatus:", loadingStatus);
  }, [merchantData, hasSavedToken, loadingStatus]);

Why this is helpful:

  • Debug state changes in real-time
  • Verify data structure before rendering
  • Track authentication flow

🚪 Logout Function

  const handleLogout = async () => {
    console.log("🚪 Logout initiated");
    await tokenService.clearToken();  // Remove from storage
    setHasSavedToken(false);          // Reset auth state
    setMerchantData(null);            // Clear merchant data
    console.log("🚪 Token cleared, state reset");
  };

Note: This function exists but isn't used in this example. You can add a logout button in your UI.


🎨 Render Logic - Step by Step

1️⃣ Token Invalid Reset

  if (tokenInvalid) {
    console.log("🔄 Token invalid state, resetting after timeout");
    setTimeout(() => setTokenInvalid(false), 100);
  }

Purpose: Reset the invalid flag after a brief moment to show the form again.

2️⃣ Loading States

  if (
    !airxpay ||
    airxpay.loading ||
    !isReady ||
    isSubmitting ||
    merchantLoading ||
    (hasSavedToken && loadingStatus)
  ) {
    console.log("⏳ Loading state active");
    return (
      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
        <ActivityIndicator size="large" color="#0066CC" />
      </View>
    );
  }

When Loading Shows:

  • SDK not initialized
  • SDK still loading
  • Not ready yet
  • Submitting form
  • Merchant operations in progress
  • Fetching status with token

3️⃣ Invalid Configuration

  if (!airxpay.isValid) {
    console.log("❌ AirXPay configuration invalid");
    return (
      <View
        style={{
          flex: 1,
          justifyContent: "center",
          alignItems: "center",
          padding: 20,
        }}
      >
        <Text style={{ color: "red", fontSize: 16, textAlign: "center" }}>
          ❌ Invalid AirXPay configuration
        </Text>
        <Text style={{ marginTop: 10, color: "#666", textAlign: "center" }}>
          Error: {airxpay.error || "Public key verification failed"}
        </Text>
      </View>
    );
  }

Shows when: Public key is invalid or verification fails.


🎉 Success Screen - With Custom Buttons

  if (hasSavedToken && merchantData && !tokenInvalid) {
    console.log(
      "✅ Rendering OnboardingCompleteScreen with extracted data:",
      JSON.stringify(merchantData, null, 2),
    );

    return (
      <OnboardingCompleteScreen
        developerData={merchantData}     // Merchant data to display
        loading={false}
        buttons={[
          {
            label: "wallets",
            onPress: () => navigation.navigate("wallet" as never),
            backgroundColor: "#0080ff",
            width: "40%",
            height: 50,
            fontSize: 15,
            gap: 8,
            fontWeight: 300,
            marginTop: -20,
            icon: "contactless-payment",
            iconPosition: "left",
            position: "absolute",
            left: 20,                    // Position left
          },
          {
            label: "Transactions",
            onPress: () => navigation.navigate("Transaction" as never),
            backgroundColor: "#ff0000",
            width: "40%",
            height: 50,
            fontSize: 15,
            gap: -8,
            fontWeight: 300,
            marginTop: -20,
            icon: "history",
            iconPosition: "left",
            position: "absolute",
            right: 20,                    // Position right
          },
        ]}
      />
    );
  }

🎯 Button Customization Explained:

| Property | Value | Effect | |----------|-------|--------| | backgroundColor | #0080ff / #ff0000 | Custom colors per button | | width | "40%" | Half-width buttons | | position | "absolute" | Float over content | | left / right | 20 | Position from edges | | gap | 8 / -8 | Space between icon & text | | icon | Material icon name | Add icons to buttons | | iconPosition | "left" | Icon on left side |

Result: Two buttons floating at bottom, positioned left and right!


📝 Form Submission Handler

  const handleSubmitToBackend = async (formData: any) => {
    console.log(
      "📤 Submitting form data to backend:",
      JSON.stringify(formData, null, 2),
    );

    try {
      setIsSubmitting(true);
      
      // 🔄 Merge with previous step data
      const mergedData = { ...fullFormData, ...formData };
      
      // 🧹 Clean empty values
      const finalData: Record<string, any> = {};
      Object.entries(mergedData).forEach(([k, v]) => {
        if (v !== "" && v != null) finalData[k] = v;
      });

      console.log(
        "📤 Final data to submit:",
        JSON.stringify(finalData, null, 2),
      );

      // 📡 Send to your backend
      const response = await fetch(
        "http://172.20.10.12:5000/api/payout-portal/wallet-setup/sellers",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            seller: finalData,
            publicKey: airxpay.publicKey,
          }),
        },
      );

      console.log("📡 Submit response status:", response.status);
      const data = await response.json();
      
      if (!response.ok) throw new Error(data.message || "Backend API failed");

      // 🔑 Save token if received
      if (data.token) {
        console.log("🔑 Token received from backend, saving...");
        await tokenService.saveToken(data.token);
        console.log("🔑 Token saved, fetching merchant data...");
        await fetchMerchantData(data.token);
      }

      return data;
    } catch (err: any) {
      console.error("❌ Backend error:", err.message);
      throw err;
    } finally {
      setIsSubmitting(false);
    }
  };

🔑 Key Points:

  1. Data Accumulation - fullFormData stores data from all steps
  2. Clean Data - Remove empty/null values before sending
  3. Token Storage - Save token immediately after success
  4. Auto-fetch - Fetch merchant data after token save
  5. Error Propagation - Throw errors for SDK to handle

🎯 Step Handlers

  const handleNext = (data: any, step: number) => {
    console.log(`➡️ Step ${step} completed with data:`, data);
    setFullFormData((prev) => ({ ...prev, ...data }));
  };

  const handleBack = (step: number) => console.log("⬅️ Back step", step);
  
  const handleComplete = (merchantData: any) =>
    console.log("✅ Onboarding complete", merchantData);

📊 What These Do:

| Handler | Purpose | |---------|---------| | handleNext | Accumulates data from each step | | handleBack | Log navigation (useful for analytics) | | handleComplete | Final completion callback |


🚀 Final Render - Onboarding Form

  console.log("🔄 Rendering MerchantOnboarding form");
  return (
    <MerchantOnboarding
      mode={(airxpay.mode as "test" | "live") || "test"}
      isKycCompleted={false}
      isBankDetailsCompleted={false}
      kycStatus="not_submitted"
      status="pending"
      onNext={handleNext}
      onBack={handleBack}
      onComplete={handleComplete}
      onSubmitToBackend={handleSubmitToBackend}
      initialData={{
        merchantName: "",
        businessName: "",
        merchantEmail: "",
        businessType: "individual",
        country: "India",
        nationality: "Indian",
      }}
    />
  );

🎨 Complete Flow Diagram

┌─────────────────┐
│   App Starts    │
└────────┬────────┘
         ↓
┌─────────────────┐
│  Check Token    │ ← tokenService.getToken()
└────────┬────────┘
         ↓
    ┌────┴────┐
    ↓         ↓
  Has      No Token
  Token      ↓
    ↓    ┌─────────────┐
┌─────────────┐  Show Form
│ Verify Token│  (5 Steps)
│ fetchStatus │    ↓
└────────┬────┘  Submit
         ↓        ↓
    ┌────┴────┐  Save Token
    ↓         ↓    ↓
 Valid    Invalid  ↓
 Token    Token    ↓
   ↓         ↓     ↓
┌─────────────┐   ↓
│ Show Success│←──┘
│ Screen with │
│ 2 Custom    │
│ Buttons     │
└─────────────┘

⚠️ Critical Rules - DO's and DON'Ts

DO:

  1. Always wrap with Provider

    <AirXPayProvider publicKey="your_key">
      <MerchantOnboardingScreen />
    </AirXPayProvider>
  2. Use useAirXPaySafe() - It returns null if provider missing

  3. Check token on mount - Resume sessions automatically

  4. Verify token validity - Don't assume stored token works

  5. Accumulate form data - Use state to combine all steps

  6. Clean data before submit - Remove empty values

DON'T:

  1. Don't use useAirXPay() without provider - It throws error
  2. Don't ignore token expiration - Always verify
  3. Don't store sensitive data - Use tokenService
  4. Don't modify SDK internals - Use provided props
  5. Don't assume API response structure - Always check and extract

🎯 Customization Examples

Example 1: Different Button Styles

buttons={[
  {
    label: "Dashboard",
    onPress: goToDashboard,
    backgroundColor: "#10B981",  // Green
    icon: "view-dashboard",
    borderRadius: 30,
    width: "45%",
  },
  {
    label: "Settings",
    onPress: goToSettings,
    backgroundColor: "transparent",
    textColor: "#10B981",
    borderColor: "#10B981",
    borderWidth: 2,
    icon: "cog",
    width: "45%",
  }
]}

Example 2: Gradient Buttons

{
  label: "Upgrade",
  onPress: handleUpgrade,
  backgroundColor: "gradient",
  gradientStart: { x: 0, y: 0 },
  gradientEnd: { x: 1, y: 0 },
  icon: "rocket",
  height: 60,
  fontSize: 18,
}

Example 3: Custom Theme

theme={{
  primaryColor: "#7C3AED",    // Purple
  secondaryColor: "#A78BFA",  // Light purple
  accentColor: "#F59E0B",     // Orange
  tickColor: "#10B981",       // Green
  backgroundColor: "#F9FAFB",
  cardColor: "#FFFFFF",
  textColor: "#1F2937"
}}

📝 Summary

This implementation demonstrates:

  1. Proper SDK initialization - Wait for ready state
  2. Token persistence - Resume sessions
  3. Token validation - Verify before using
  4. Data accumulation - Combine all steps
  5. Clean submission - Remove empty values
  6. Success screen - Show merchant data
  7. Custom buttons - Full styling control
  8. Error handling - User-friendly messages
  9. Loading states - Prevent UI flicker
  10. Debug logging - Track flow in console

🚀 Quick Start Template

import React, { useEffect, useState } from "react";
import { View, ActivityIndicator } from "react-native";
import { 
  AirXPayProvider,
  MerchantOnboarding,
  useAirXPaySafe,
  tokenService,
  OnboardingCompleteScreen
} from "@flixora/airxpay-react-native";

function OnboardingFlow() {
  const airxpay = useAirXPaySafe();
  const [hasToken, setHasToken] = useState(false);
  const [merchantData, setMerchantData] = useState(null);

  useEffect(() => {
    const init = async () => {
      const token = await tokenService.getToken();
      if (token) {
        // Fetch merchant data
        const response = await fetch('your-api/status', {
          headers: { Authorization: `Bearer ${token}` }
        });
        if (response.ok) {
          const data = await response.json();
          setMerchantData(data.data || data);
          setHasToken(true);
        }
      }
    };
    init();
  }, []);

  if (!airxpay?.isValid) return <ActivityIndicator />;

  if (hasToken && merchantData) {
    return (
      <OnboardingCompleteScreen
        developerData={merchantData}
        buttons={[
          { label: "Continue", onPress: () => {} }
        ]}
      />
    );
  }

  return (
    <MerchantOnboarding
      mode="test"
      isKycCompleted={false}
      isBankDetailsCompleted={false}
      kycStatus="not_submitted"
      status="pending"
      onComplete={() => {}}
      onSubmitToBackend={async (data) => {
        const response = await fetch('your-api/merchant', {
          method: 'POST',
          body: JSON.stringify(data)
        });
        return response.json();
      }}
    />
  );
}

export default function App() {
  return (
    <AirXPayProvider publicKey="your_key">
      <OnboardingFlow />
    </AirXPayProvider>
  );
}

📚 Remember:

  • ✅ One file handles the entire flow
  • ✅ Token management is automatic
  • ✅ Custom buttons anywhere, anytime
  • ✅ Works exactly as shown in the example
  • ✅ Modify colors, positions, styles freely
  • ✅ Don't split logic across multiple files unnecessarily

This is the PROVEN way that works! Follow this pattern exactly for successful integration. 🚀

Logger

Configurable logging utility.

import { Logger } from '@flixora/airxpay-react-native';

const logger = new Logger({ 
  enabled: true, 
  prefix: '[MyApp]' 
});

logger.info('Information');
logger.warn('Warning');
logger.error('Error');
logger.debug('Debug info');

📁 File Structure

@flixora/airxpay-react-native/
├── api/
│   └── clients/
│       └── verifyPublicKey.ts      # 🔒 Hidden API (URL never exposed)
├── browsers/
│   └── fileBrowser.ts               # 📁 File handling with modern Expo API
├── components/
│   ├── common/
│   │   └── FileUploader.tsx         # 🖼️ Reusable upload component
│   └── steps/
│       ├── BasicDetailsForm.tsx     # 📝 Step 1: Basic Info
│       ├── KYCVerification.tsx       # 🆔 Step 2: KYC Documents
│       ├── BankDetails.tsx           # 🏦 Step 3: Bank Details
│       └── onboarding/
│           ├── MerchantOnboarding.tsx # 🚦 Main Flow (5 steps)
│           ├── FinalStepScreen.tsx    # ✅ Step 4: Review & Submit
│           └── OnboardingComplete.tsx # 🎉 Step 5: Success
├── contexts/
│   └── AirXPayProvider.tsx          # ⚛️ React Context Provider
├── error/
│   └── errorHandler.ts               # 🛡️ Centralized error handling
├── etc/
│   └── constants.ts                   # 📋 Constants (endpoints, texts)
├── events/
│   └── sdkEvents.ts                   # 📡 Event emitter
├── hooks/
│   ├── useAirXPay.ts                   # 🎣 SDK context hook
│   └── useMerchantOnboarding.ts        # 🎣 Merchant management hook
├── types/
│   └── merchantTypes.ts                 # 📘 TypeScript definitions
└── utils/
    ├── log/
    │   └── logger.ts                    # 🪵 Configurable logger
    └── token/
        ├── tokenService.ts               # 🔐 Secure token storage
        └── storage.ts                    # 📦 Generic storage wrapper

🔒 Hidden API (Developer Never Sees)

// api/clients/verifyPublicKey.ts
const BACKEND_URL = 'http://172.20.10.12:7000'; // 🔒 FIXED - never exposed

export const verifyPublicKey = async (publicKey: string) => {
  // Developer cannot see or change this URL
  const response = await fetch(`${BACKEND_URL}/api/merchant/verify-public-key`, {
    method: 'POST',
    body: JSON.stringify({ publicKey })
  });
  return response.json();
};

📱 Complete Example

// MerchantScreen.tsx
import React, { useState } from 'react';
import { View } from 'react-native';
import { 
  MerchantOnboarding, 
  useAirXPay,
  type Merchant 
} from '@flixora/airxpay-react-native';

export const MerchantScreen = () => {
  const [loading, setLoading] = useState(false);
  const { publicKey } = useAirXPay();

  const handleSubmitToBackend = async (data: any) => {
    // Optional: Call your backend API first
    const response = await fetch('https://api.yourserver.com/merchant', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    return response.json();
  };

  const handleComplete = (merchantData: Merchant) => {
    console.log('🎉 Merchant created:', merchantData);
    // Navigate to dashboard
  };

  return (
    <View style={{ flex: 1 }}>
      <MerchantOnboarding
        mode="test"
        isKycCompleted={false}
        isBankDetailsCompleted={false}
        kycStatus="not_submitted"
        status="pending"
        onSubmitToBackend={handleSubmitToBackend}
        onComplete={handleComplete}
        onNext={(data, step) => console.log(`Step ${step} completed`)}
        onBack={(step) => console.log(`Back to step ${step}`)}
        loading={loading}
        initialData={{
          merchantName: 'John Doe',
          merchantEmail: '[email protected]',
          country: 'India'
        }}
      />
    </View>
  );
};

📊 Version History

| Version | Date | Key Features | |---------|------|--------------| | 0.0.5 | 2026-03-03 | 🎉 Complete onboarding flow, FileUploader, All form steps, Hooks, Error handling | | 0.0.4 | 2026-02-20 | ✅ FileUploader component, MIME type fixes | | 0.0.3 | 2026-02-15 | ✅ BasicDetailsForm, Validation improvements | | 0.0.2 | 2026-02-10 | ✅ AirXPayProvider, Token service, Events | | 0.0.1 | 2026-02-01 | 🎉 Initial release |

🎯 Features by Version

✅ v0.0.5 (Current)

  • Complete 5-step onboarding flow
  • File upload with modern Expo API (no warnings)
  • KYC verification with document uploads
  • Bank details with validation
  • Final review and submission
  • Success screen with wallet details
  • React Context provider
  • Custom hooks (useAirXPay, useMerchantOnboarding)
  • Event emitter system
  • Centralized error handling
  • Secure token storage
  • Configurable logger
  • Full TypeScript support

✅ v0.0.4

  • FileUploader component
  • Image picker integration
  • Preview functionality
  • MIME type validation

✅ v0.0.3

  • BasicDetailsForm
  • Form validation
  • Country dropdown
  • Date picker

✅ v0.0.2

  • AirXPayProvider context
  • Token service
  • Event system

✅ v0.0.1

  • Project structure
  • TypeScript config
  • Public key verification

🚦 Upcoming Features (v0.1.0)

  • 🔐 Biometric authentication
  • 📱 Push notifications
  • 💳 Payment processing
  • 📊 Analytics dashboard
  • 🌐 Offline mode
  • 🎨 Theme customization

🛡️ Security Features

  • Hidden API URLs - Backend URLs never exposed to developers
  • Secure Token Storage - Uses AsyncStorage securely
  • No Token Auto-attachment - Developer controls API calls
  • Input Validation - All fields validated client-side
  • Error Handling - User-friendly error messages
  • Test Mode - Safe testing without real data

📝 TypeScript Support

All components and hooks are fully typed:

import type { 
  Merchant, 
  KycStatus, 
  BankDetails,
  AppError,
  StepCompletion 
} from '@flixora/airxpay-react-native';

// Use types in your code
const merchant: Merchant = {
  merchantId: 'mch_123',
  merchantName: 'John Doe',
  // ...
};

🐛 Troubleshooting

Common Issues

  1. "Public key is required"

    • Make sure to wrap your app with AirXPayProvider and provide a valid public key
  2. File upload fails

    • Check permissions in app.json:
    {
      "expo": {
        "plugins": [
          [
            "expo-image-picker",
            {
              "photosPermission": "Allow $(PRODUCT_NAME) to access your photos"
            }
          ]
        ]
      }
    }
  3. TypeScript errors

    • Update to latest version: npm install @flixora/airxpay-react-native@latest
    • Check tsconfig.json includes "skipLibCheck": true

🤝 Contributing

Found a bug? Have a feature request?

📄 License

MIT © Flixora

📞 Support


Made with ❤️ by Flixora Team