React Native Integration
Learn how to integrate Kontigo Wallet Checkout in your React Native application
1. Install Required Dependencies
# Install the WebView package
npm install react-native-webview
# For iOS, install pods
cd ios && pod install && cd ..
2. Set Up iOS and Android
Some native configuration is required for the WebView to work properly on iOS and Android.
2.1. iOS Configuration
To allow loading content from the Kontigo domain, update your App Transport Security settings in ios/<YourProjectName>/Info.plist.
Add the following inside the <dict>
tag:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>wallet.kontigo.lat</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
2.2. Android Configuration
Ensure internet access is enabled by adding this line to your AndroidManifest.xml
, inside the <manifest>
tag:
<uses-permission android:name="android.permission.INTERNET" />
3. Messages from Kontigo to Your Application
The Kontigo checkout webview communicates with your application through the window.postMessage
API. This page explains the messages exchanged between your application and the Kontigo checkout webview.
{
event: KontigoAppMessageEvent.WEBVIEW_LOADED,
payload: string
}
{
event: KontigoAppMessageEvent.USER_AUTHENTICATED,
payload: {
email: string;
isAuthenticated: boolean;
}
}
{
event: KontigoAppMessageEvent.QUOTE_CREATED,
payload: {
id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
amount: 1000, // $10.00 USDC in minor units
type: "transfer",
status: "pending"
}
}
{
event: KontigoAppMessageEvent.QUOTE_CONFIRMED,
payload: {
id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
amount: 1000,
type: "transfer",
status: "pending"
}
}
{
event: KontigoAppMessageEvent.QUOTE_PAID,
payload: {
id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
amount: 1000,
type: "transfer",
status: "processing"
}
}
{
event: KontigoAppMessageEvent.QUOTE_ERROR,
payload: {
id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
name: "",
message: "service unavailable",
}
}
{
event: KontigoAppMessageEvent.WEBVIEW_UNLOADED,
payload: string
}
4. Defining the Component
Create a component that loads the Kontigo WebView and listens for messages using the onMessage prop.
import { useCallback, useRef } from "react";
import { Linking, StyleSheet } from "react-native";
import WebView, { WebViewNavigation } from "react-native-webview";
export type Quote = {
id: string;
status: QuoteStatus;
type: QuoteType;
amount: string;
};
export type User = {
email: string;
isAuthenticated: boolean;
}
export type QuoteStatus = "pending" | "completed" | "failed" | "processing";
export type QuoteType = "transfer" | "withdraw" | "deposit";
export type QuoteError = {
name: string;
message: string;
stack?: string;
};
export enum KontigoAppMessageEvent {
// Lifecycle events
WEBVIEW_LOADED = "WEBVIEW_LOADED",
WEBVIEW_UNLOADED = "WEBVIEW_UNLOADED",
// User events
USER_AUTHENTICATED = "USER_AUTHENTICATED",
// Quote events
QUOTE_CREATED = "QUOTE_CREATED",
QUOTE_CONFIRMED = "QUOTE_CONFIRMED",
QUOTE_PAID = "QUOTE_PAID",
QUOTE_ERROR = "QUOTE_ERROR",
}
type AppMessage =
| { event: KontigoAppMessageEvent.WEBVIEW_LOADED; payload: string }
| { event: KontigoAppMessageEvent.WEBVIEW_UNLOADED; payload: string }
| { event: KontigoAppMessageEvent.USER_AUTHENTICATED; payload: User }
| { event: KontigoAppMessageEvent.QUOTE_CREATED; payload: Quote }
| { event: KontigoAppMessageEvent.QUOTE_CONFIRMED; payload: Quote }
| { event: KontigoAppMessageEvent.QUOTE_PAID; payload: Quote }
| { event: KontigoAppMessageEvent.QUOTE_ERROR; payload: QuoteError };
interface KontigoWebViewProps {
/**
* The user's email address, passed for identification.
* The user will not be able to log in to Kontigo with a different
* email than the one provided here for this WebView session.
*/
userEmail: string;
/**
* Callback function invoked when a quote is successfully paid.
* Receives the completed Quote object as an argument.
*/
onQuotePaid?: (quote: Quote) => void;
/**
* Callback function invoked when an error occurs during the Kontigo flow.
* Receives a QuoteError object as an argument.
*/
onQuoteError?: (error: QuoteError) => void;
/**
* Callback function invoked when the Kontigo webview has finished loading.
*/
onWebviewLoaded?: () => void;
/**
* Callback function invoked when the Kontigo webview is unloaded.
*/
onWebviewUnloaded?: () => void;
// Add any other relevant props here.
}
const CUSTOM_SCHEME = "";
const MESSAGE_PATH = "";
const PARTNER_ID = "";
const KONTIGO_BASE_URL = "https://app.kontigo.lat/";
const ORIGIN_WHITELIST = ["*"];
export function KontigoWebView({
userEmail,
onQuotePaid,
onQuoteError,
onWebviewLoaded,
onWebviewUnloaded,
}: KontigoWebViewProps) {
const webViewRef = useRef<WebView>(null);
const url = new URL(KONTIGO_BASE_URL);
url.searchParams.append("userEmail", userEmail);
url.searchParams.append("partnerId", PARTNER_ID);
const kontigoUrl = url.toString();
const handleKontigoAppMessage = useCallback(
(message: AppMessage) => {
try {
switch (message.event) {
case KontigoAppMessageEvent.WEBVIEW_LOADED:
console.log("KontigoApp: WebView loaded.", message.payload);
if (onWebviewLoaded) onWebviewLoaded();
break;
case KontigoAppMessageEvent.USER_AUTHENTICATED:
console.log("KontigoApp: User authenticated.", message.payload);
break;
case KontigoAppMessageEvent.QUOTE_CREATED:
console.log(
`KontigoApp: QUOTE_CREATED, Quote ID: ${message.payload.id}, Status: ${message.payload.status}, Type: ${message.payload.type}, Amount: ${message.payload.amount}`
);
break;
case KontigoAppMessageEvent.QUOTE_CONFIRMED:
console.log(
`KontigoApp: QUOTE_CONFIRMED, Quote ID: ${message.payload.id}, Status: ${message.payload.status}`
);
break;
case KontigoAppMessageEvent.QUOTE_PAID:
console.log(
`KontigoApp: QUOTE_PAID, Quote ID: ${message.payload.id}, Status: ${message.payload.status}`
);
if (onQuotePaid) onQuotePaid(message.payload);
break;
case KontigoAppMessageEvent.QUOTE_ERROR:
console.error(
`KontigoApp: QUOTE_ERROR, Name: ${message.payload.name}, Message: ${message.payload.message}`
);
if (onQuoteError) onQuoteError(message.payload);
break;
case KontigoAppMessageEvent.WEBVIEW_UNLOADED:
console.log("KontigoApp: WebView unloaded.", message.payload);
if (onWebviewUnloaded) onWebviewUnloaded();
break;
default:
console.warn("KontigoApp: Unknown event received:", message);
break;
}
} catch (error) {
console.error(
"Failed to parse message from KontigoApp:",
error,
"Raw data:",
message
);
}
},
[onQuotePaid, onQuoteError, onWebviewLoaded, onWebviewUnloaded]
);
/**
* Handles intercepted URL requests.
* Checks for our custom scheme and extracts message data.
* Returns `false` for message URLs to cancel navigation.
* Returns `true` for other URLs to allow navigation.
*/
const handleShouldStartLoadWithRequest = useCallback(
(request: WebViewNavigation) => {
const url = request.url;
if (
url.startsWith("googlegmail://") ||
url.startsWith("ms-outlook://") ||
url.startsWith("ymail://") ||
url.startsWith("mailto:")
) {
Linking.openURL(url);
return false;
}
console.log("WebView: Attempting to load URL:", url);
if (url.startsWith(`${CUSTOM_SCHEME}://${MESSAGE_PATH}`)) {
try {
const urlObject = new URL(url);
const encodedJsonData = urlObject.searchParams.get("data");
if (!encodedJsonData) return false;
const jsonDataString = decodeURIComponent(encodedJsonData);
const parsedData = JSON.parse(jsonDataString) as AppMessage;
handleKontigoAppMessage(parsedData);
// **IMPORTANT:** Return false to PREVENT the WebView from navigating to this URL.
// This keeps the WebView on the current page.
return false;
} catch (error) {
console.error(
"WebView: Failed to process custom message URL:",
error
);
return false;
}
}
// If it's not our custom message URL, allow the WebView to load it normally
return true;
},
[handleKontigoAppMessage]
);
return (
<WebView
ref={webViewRef}
source={{ uri: kontigoUrl }}
style={styles.webView}
originWhitelist={ORIGIN_WHITELIST}
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
/>
);
}
const styles = StyleSheet.create({
webView: {
flex: 1,
},
});
5. Implement the Component
Step 5.1: Import Component and Supporting Types
Import the KontigoWebView
component, React, and React Native dependencies.
import React, { useState, useCallback } from 'react';
import { View, Button, Alert, StyleSheet, Text, SafeAreaView, Modal } from 'react-native';
import {
KontigoWebView,
Quote,
QuoteError,
} from './components/KontigoWebView';
Step 5.2: Prepare Required Props and State
Before rendering the component:
- Create state to control the
KontigoWebView
visibility (e.g., using a Modal). - Get the user’s email (usually from your auth state).
- Define callback functions for handling message events.
const MyScreenUsingKontigo = () => {
const [showKontigoWebView, setShowKontigoWebView] = useState(false);
// IMPORTANT: Replace with the actual logged-in user's email from your app's state/context
const currentUserEmail = "[email protected]";
const handleQuotePaid = useCallback((quote: Quote) => {
console.log("HostApp: Quote Paid successfully!", quote);
}, []);
const handleQuoteError = useCallback((error: QuoteError) => {
console.error("HostApp: An error occurred with the quote.", error);
}, []);
const handleWebviewLoaded = useCallback(() => {
console.log("HostApp: Kontigo WebView has finished loading.");
}, []);
const handleWebviewUnloaded = useCallback(() => {
console.log("HostApp: Kontigo WebView has been unloaded.");
}, []);
// ... rest of your component logic and JSX in Step 5.3
Step 5.3: Add the Component to Your JSX
Once your state and handlers are ready, render KontigoWebView
. It can be wrapped in a Modal or shown inline depending on your UX needs.
// Continuing from MyScreenUsingKontigo in Step 5.2:
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
<Text style={styles.headerText}>Kontigo Integration Example</Text>
<Text style={styles.infoText}>
User Email: {currentUserEmail}
</Text>
<Button
title="Open Kontigo Wallet"
onPress={() => setShowKontigoWebView(true)}
/>
</View>
<Modal
animationType="slide"
transparent={false}
visible={showKontigoWebView}
onRequestClose={() => {
setShowKontigoWebView(false);
}}
>
<SafeAreaView style={styles.webviewSafeArea}>
<KontigoWebView
userEmail={currentUserEmail}
onQuotePaid={handleQuotePaid}
onQuoteError={handleQuoteError}
onWebviewLoaded={handleWebviewLoaded}
onWebviewUnloaded={handleWebviewUnloaded}
/>
<Button title="Close Window" onPress={() => setShowKontigoWebView(false)} />
</SafeAreaView>
</Modal>
</SafeAreaView>
);
};
// Example Styles
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#f0f0f0',
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
headerText: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 10,
},
infoText: {
marginBottom: 20,
textAlign: 'center',
fontSize: 16,
},
webviewSafeArea: {
flex: 1,
},
// If not using a Modal, your webviewContainer might look like this:
// webviewContainer: {
// position: 'absolute',
// top: 0,
// left: 0,
// right: 0,
// bottom: 0,
// backgroundColor: 'white',
// },
});
Example

Updated 11 days ago