Snap can also be embedded within your mobile app using WebView.
- Demo for native applications (Android and iOS)
- Demo for cross-platform applications (Flutter and React Native)
Note
Snap uses JavaScript in order to run properly. If you having issues accessing the Snap page in order to check out you may need to enable JavaScript
webView.settings.javaScriptEnabled = true
webView.settings.javaScriptCanOpenWindowsAutomatically = true
webView.settings.domStorageEnabled = true
webView.settings.setAppCacheEnabled(true)
webView.settings.databaseEnabled = true
webView.settings.allowFileAccessFromFileURLs = true
webView.settings.allowFileAccess = true
webView.settings.allowContentAccess = true
webView.settings.cacheMode = WebSettings.LOAD_NO_CACHE
// react-native
<WebView
...
...
javaScriptEnabled={true}
javaScriptCanOpenWindowsAutomatically={true}
domStorageEnabled={true}
cacheEnabled={true}
allowFileAccessFromFileURLs={true}
allowFileAccess={true}
cacheMode="LOAD_NO_CACHE"
>
// flutter
WebView(
...
...
javascriptMode: JavascriptMode.unrestricted,
)
Custom JS Injection
We strongly discourage merchant from injecting any custom JS into the Webview during payment flow. It is expected that Merchant will honor customer’s privacy by not tracking any input that customer made within the Webview especially on sensitive data, as legal consequences could follow
Download File inside Webview
Note
Snap offers downloadable files within the app. By default, these files will automatically download when accessed through a web browser. However, if you are using a webview within the app, additional configuration is required for the download to work properly.
class SnapWebViewScreen extends StatefulWidget {
@override
State<SnapWebViewScreen> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<SnapWebViewScreen> {
late WebViewController _controller;
@override
Widget build(BuildContext context) {
return Scaffold(
body: WebView(
initialUrl: 'your-url',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller = webViewController;
},
javascriptChannels: <JavascriptChannel>{
_blobDataChannel(context),
},
navigationDelegate: (NavigationRequest request) {
final host = Uri.parse(request.url).toString();
if (host.startsWith('blob:')) {
_fetchBlobData(request.url); // Fetch and handle blob data.
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
);
}
// Javascript channel for communicating between JS and Flutter
JavascriptChannel _blobDataChannel(BuildContext context) {
return JavascriptChannel(
name: 'BlobDataChannel',
onMessageReceived: (JavascriptMessage message) async {
final decodedBytes = base64Decode(message.message);
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/downloaded_file.csv');
await file.writeAsBytes(decodedBytes);
await FileSaver.instance.saveFile('downloaded_file', decodedBytes, 'csv');
},
);
}
// Fetch blob data using JS and send it to Flutter using the channel
void _fetchBlobData(String blobUrl) async {
final script = '''
(async function() {
const response = await fetch('$blobUrl');
const blob = await response.blob();
const reader = new FileReader();
reader.onloadend = function() {
const base64data = reader.result.split(',')[1];
BlobDataChannel.postMessage(base64data);
};
reader.readAsDataURL(blob);
})();
''';
_controller.runJavascript(script);
}
}
import React, { useState } from 'react';
import { View, Alert, StyleSheet, ActivityIndicator } from 'react-native';
import { WebView } from 'react-native-webview';
import RNFS from 'react-native-fs';
import RNFetchBlob from 'rn-fetch-blob';
const WebviewComponent = ({ uri }) => {
const [isLoading, setLoading] = useState(true);
// Handle file download for 'blob:' URLs
const handleDownload = async (url) => {
const downloadDest = `${RNFS.DocumentDirectoryPath}/${new Date().getTime()}.png`; // Save path
RNFetchBlob.config({
fileCache: true,
path: downloadDest, // Download destination path
})
.fetch('GET', url) // Fetch the file from the URL
.then((res) => {
Alert.alert('Download Complete', `File downloaded to: ${res.path()}`);
})
.catch((error) => {
Alert.alert('Download Error', error.message);
});
};
return (
<View style={styles.wrapper}>
<WebView
source={{ uri: uri || DEFAULT_URI }}
onLoad={() => setLoading(false)}
javaScriptEnabled={true}
domStorageEnabled={true}
onShouldStartLoadWithRequest={(request) => {
// Intercept and handle 'blob:' URLs for download
if (request.url.startsWith('blob:')) {
handleDownload(request.url); // Call download handler
return false; // Prevent default navigation
}
return true; // Allow other URLs to load
}}
/>
{isLoading && (
<View style={styles.loader}>
<ActivityIndicator size='large' color='blue' />
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
wrapper: { flex: 1 },
loader: {
position: 'absolute',
top: '50%',
right: 0,
left: 0,
},
});
export default WebviewComponent;
Invalid Routing blob
We recommend merchants implement this workaround, especially when using QRIS, Alfamart, or Indomaret payment channels, as these channels involve downloading files such as QR codes, barcode images, and PDF documents.