Webview

Snap can also be embedded within your mobile app using WebView.


  1. Demo for native applications (Android and iOS)
  2. 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.