Web-View Integration
Integrate Amwal Installment Links directly into your native iOS, Android, or React Native mobile applications using WebViews without redirecting users out to external mobile browsers.
1. Integration Architecture
Rendering diagram...
2. Generate Payment Link (Backend)
Generate a single-use installment payment URL from your server:
const createPaymentLink = async (orderData) => {
const response = await fetch('https://backend.sa.amwal.tech/payment_links/YOUR_STORE_ID/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'YOUR_SECRET_KEY',
},
body: JSON.stringify({
amount: orderData.amount,
title: orderData.title,
singleUse: true,
client_phone_number: orderData.customerPhone,
client_email: orderData.customerEmail,
language: 'en', // 'en' or 'ar'
callback_url: 'https://mystore.sa/webhook/payment-complete',
metadata: {
order_id: orderData.orderId,
platform: 'mobile_app'
}
})
});
return await response.json();
};3. Mobile Implementation
iOS (Swift - WKWebView)
import UIKit
import WebKit
class PaymentViewController: UIViewController, WKNavigationDelegate {
@IBOutlet weak var webView: WKWebView!
var paymentURL: String?
var onPaymentComplete: ((Bool, String?) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
webView.configuration.preferences.javaScriptEnabled = true
webView.customUserAgent = "StoreMobileApp/1.0 Mobile"
if let urlString = paymentURL, let url = URL(string: urlString) {
webView.load(URLRequest(url: url))
}
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
// Intercept completion redirects
if url.absoluteString.contains("payment-success") {
onPaymentComplete?(true, nil)
dismiss(animated: true)
decisionHandler(.cancel)
return
} else if url.absoluteString.contains("payment-failed") {
onPaymentComplete?(false, "Payment cancelled or declined")
dismiss(animated: true)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}React Native (react-native-webview)
import React, { useRef } from 'react';
import { View, StyleSheet, Alert } from 'react-native';
import { WebView } from 'react-native-webview';
interface PaymentWebViewProps {
paymentURL: string;
onSuccess: () => void;
onError: (error: string) => void;
}
export const PaymentWebView = ({ paymentURL, onSuccess, onError }: PaymentWebViewProps) => {
const handleNavigationStateChange = (navState: { url: string }) => {
const { url } = navState;
if (url.includes('payment-success')) {
onSuccess();
} else if (url.includes('payment-failed') || url.includes('payment-cancelled')) {
onError('Payment was cancelled or failed.');
}
};
return (
<View style={styles.container}>
<WebView
source={{ uri: paymentURL }}
style={styles.webView}
onNavigationStateChange={handleNavigationStateChange}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1 },
webView: { flex: 1 },
});4. Best Practices for Mobile WebViews
- Always Verify Server-Side: Never trust the mobile client alone to mark an order as paid. Confirm final status through backend webhooks (
order.success) orPOST /payment_links/{payment_link_id}/details. - Single Use Links: Set
singleUse: trueto prevent replay attempts on completed payment links. - Session Cookies: Enable
domStorageEnabled = trueandjavaScriptEnabled = trueso biometric and 3DS verification frames load without issues.
