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:
| Option | Interface Pattern | Recommended Application |
|---|---|---|
| Option A | Browser Full Redirect | Traditional multi-page e-commerce websites and standard checkout carts. |
| Option B | Responsive Iframe Embed | Single-page web applications (SPAs) desiring an in-page checkout modal. |
| Option C | JavaScript SDK (snap.js) | Websites needing seamless modal popups with JavaScript lifecycle callbacks. |
| Option D | Mobile In-App WebView | iOS, 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:
// 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
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:
<!-- 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:
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),
);
}
}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;
}}
/>
);
}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
}
}