Front-End Integration

Presenting the checkout experience to your buyers: web browser redirect, responsive iframe modal, Midtrans-style JavaScript SDK (snap.js), and mobile app in-app webview deep linking.

Overview of presentation options

When an order is created, your server receives data.url containing an encrypted, short-lived session token. Choose the presentation approach that best suits your user interface:

OptionInterface PatternRecommended Application
Option ABrowser Full RedirectTraditional multi-page e-commerce websites and standard checkout carts.
Option BResponsive Iframe EmbedSingle-page web applications (SPAs) desiring an in-page checkout modal.
Option CJavaScript SDK (snap.js)Websites needing seamless modal popups with JavaScript lifecycle callbacks.
Option DMobile In-App WebViewiOS, Android, Flutter, and React Native mobile applications.

Option A — Full browser redirect (Recommended for web)

Redirect the user's browser directly to data.url. You can append an optional &redirectUrl= query parameter specifying the return destination on your website once payment concludes:

Client-side Web Redirect
// Obtain data.url from your backend order creation endpoint
const checkoutUrl = data.url + "&redirectUrl=" + encodeURIComponent("https://mystore.com/orders/INV-2026-0001/complete");

// Redirect the customer
window.location.assign(checkoutUrl);

Option B — Responsive iframe embed

Embed the payment checkout directly inside your page layout or custom modal dialog. Ensure you include allow="clipboard-write" so users can copy deposit addresses with one click:

Iframe Modal Markup
<iframe
  src="https://webview.hashpay.id/?key=pl_eyJhbGciOi...&redirectUrl=https%3A%2F%2Fmystore.com%2Fcomplete"
  style="width: 100%; height: 740px; border: 0; border-radius: 16px; overflow: hidden;"
  allow="clipboard-write"
></iframe>

Option C — JavaScript SDK (snap.js)

Load our lightweight Midtrans-Snap-style client library. The script exposes window.pay for production and window.paytest for sandbox testing:

Snap.js Integration
<!-- Include the Hashpay Snap library in your HTML header -->
<script src="https://webview.hashpay.id/snap/snap.js"></script>

<script>
  function openHashpayModal(orderKey) {
    // For Sandbox, use window.paytest(); for Production, use window.pay()
    window.pay(
      { key: orderKey },
      {
        onSuccess: function (result) {
          console.log("Payment detected successfully:", result);
          window.location.href = "/payment-success?order=" + result.order_no;
        },
        onPending: function (result) {
          console.log("Transaction pending on-chain confirmation:", result);
        },
        onError: function (error) {
          console.error("Payment failed or aborted:", error);
          alert("Payment could not be completed. Please try again.");
        },
        onClose: function () {
          console.log("Customer closed the checkout modal without paying.");
        }
      }
    );
  }
</script>

Option D — Mobile in-app webview & deep link handling

When hosting the checkout webview inside native mobile applications (Flutter, React Native, Android Kotlin, or iOS Swift), Web3 wallet connection buttons trigger custom URI schemes such as ethereum:, solana:, wc:, metamask:, and trust:. Standard mobile webviews reject non-HTTP schemes by default. You MUST intercept these URLs and launch native wallet apps:

Flutter WebView Implementation (webview_flutter)
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';

class HashpayCheckoutScreen extends StatefulWidget {
  final String checkoutUrl;
  const HashpayCheckoutScreen({Key? key, required this.checkoutUrl}) : super(key: key);

  @override
  State<HashpayCheckoutScreen> createState() => _HashpayCheckoutScreenState();
}

class _HashpayCheckoutScreenState extends State<HashpayCheckoutScreen> {
  late final WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onNavigationRequest: (NavigationRequest request) async {
            final uri = Uri.parse(request.url);
            // Intercept non-HTTP Web3 deep links (MetaMask, Trust, Phantom, etc.)
            if (!request.url.startsWith('http://') && !request.url.startsWith('https://')) {
              if (await canLaunchUrl(uri)) {
                await launchUrl(uri, mode: LaunchMode.externalApplication);
                return NavigationDecision.prevent;
              }
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse(widget.checkoutUrl));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Crypto Checkout')),
      body: WebViewWidget(controller: _controller),
    );
  }
}
React Native WebView Implementation (react-native-webview)
import React from 'react';
import { Linking } from 'react-native';
import { WebView } from 'react-native-webview';

export function HashpayCheckoutModal({ checkoutUrl, onPaymentClosed }) {
  return (
    <WebView
      source={{ uri: checkoutUrl }}
      javaScriptEnabled={true}
      domStorageEnabled={true}
      onShouldStartLoadWithRequest={(request) => {
        // Intercept Web3 wallet schemes
        if (!request.url.startsWith('http://') && !request.url.startsWith('https://')) {
          Linking.openURL(request.url).catch((err) =>
            console.warn('Could not launch crypto wallet app:', err)
          );
          return false; // Prevent internal webview navigation crash
        }
        return true;
      }}
    />
  );
}
Native Android Kotlin Implementation (WebViewClient)
webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
        val url = request?.url?.toString() ?: return false
        if (!url.startsWith("http://") && !url.startsWith("https://")) {
            try {
                val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
                view?.context?.startActivity(intent)
                return true
            } catch (e: ActivityNotFoundException) {
                Toast.makeText(view?.context, "No compatible Web3 wallet installed", Toast.LENGTH_SHORT).show()
                return true
            }
        }
        return false
    }
}