Web-View Integration
Overview
This guide demonstrates how to integrate Amwal Payment Links into your mobile application using web-view for online store transactions. The integration allows customers to complete payments seamlessly within your mobile app without redirecting to external browsers.
Payment Flow

Integration Architecture

Prerequisites
- Amwal merchant account with API credentials
- Mobile development environment (iOS/Android/React Native)
API Configuration
Base URL
Production: https://backend.sa.amwal.tech
Authentication
Include your API key in the request headers:
Authorization: your_api_key_here
Optional Environment Key
For sandbox testing, include:
X-Amwal-Key: sandbox-amwal-xxx
Step 1: Create Payment Link
API Endpoint
POST /payment_links/{store_id}/create
Request Example
JavaScript/Node.js
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_api_key_here',
// 'X-Amwal-Key': 'sandbox-amwal-xxx' // For testing
},
body: JSON.stringify({
amount: orderData.amount,
title: orderData.title,
description: orderData.description,
singleUse: true,
selectedDate: orderData.expiryDate,
client_phone_number: orderData.customerPhone,
client_email: orderData.customerEmail,
client_first_name: orderData.customerFirstName,
client_last_name: orderData.customerLastName,
language: 'en', // or 'ar'
send_sms: false,
send_receipt_sms: false,
callback_url: 'https://yourstore.com/webhook/payment-complete',
metadata: {
order_id: orderData.orderId,
customer_id: orderData.customerId,
platform: 'mobile_app'
}
})
});
return await response.json();
};
// Usage
const orderData = {
amount: 299.99,
title: "Order #12345",
description: "Premium Headphones - Wireless",
expiryDate: "2024-12-31T23:59:59Z",
customerPhone: "+966501234567",
customerEmail: "[email protected]",
customerFirstName: "Ahmed",
customerLastName: "Al-Rashid",
orderId: "ORD-12345",
customerId: "CUST-789"
};
createPaymentLink(orderData)
.then(result => {
console.log('Payment Link Created:', result.url);
// Open in web-view
openPaymentWebView(result.url);
})
.catch(error => console.error('Error:', error));
Step 2: Mobile Web-View Integration
iOS (Swift)
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
// Configure web view for payment
setupWebView()
if let urlString = paymentURL, let url = URL(string: urlString) {
let request = URLRequest(url: url)
webView.load(request)
}
}
private func setupWebView() {
// Enable JavaScript
webView.configuration.preferences.javaScriptEnabled = true
// Set user agent for mobile optimization
webView.customUserAgent = "YourAppName/1.0 Mobile"
}
// Handle navigation changes
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
// Check for payment completion redirects
if url.absoluteString.contains("payment-success") {
handlePaymentSuccess()
decisionHandler(.cancel)
return
} else if url.absoluteString.contains("payment-failed") {
handlePaymentFailure("Payment was cancelled or failed")
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
private func handlePaymentSuccess() {
DispatchQueue.main.async {
self.onPaymentComplete?(true, nil)
self.dismiss(animated: true)
}
}
private func handlePaymentFailure(_ error: String) {
DispatchQueue.main.async {
self.onPaymentComplete?(false, error)
self.dismiss(animated: true)
}
}
}
// Usage in your main view controller
func openPaymentWebView(paymentURL: String) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let paymentVC = storyboard.instantiateViewController(withIdentifier: "PaymentViewController") as? PaymentViewController {
paymentVC.paymentURL = paymentURL
paymentVC.onPaymentComplete = { [weak self] success, error in
if success {
self?.showAlert(title: "Payment Successful", message: "Your order has been processed!")
// Refresh order status
self?.refreshOrderStatus()
} else {
self?.showAlert(title: "Payment Failed", message: error ?? "Unknown error occurred")
}
}
present(paymentVC, animated: true)
}
}
Android (Java)
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebSettings;
public class PaymentActivity extends AppCompatActivity {
private WebView webView;
private String paymentURL;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);
webView = findViewById(R.id.payment_webview);
paymentURL = getIntent().getStringExtra("payment_url");
setupWebView();
loadPaymentURL();
}
private void setupWebView() {
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setUserAgentString("YourAppName/1.0 Mobile");
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Handle payment completion
if (url.contains("payment-success")) {
handlePaymentSuccess();
return true;
} else if (url.contains("payment-failed")) {
handlePaymentFailure("Payment was cancelled or failed");
return true;
}
return false;
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Hide loading indicator
}
});
}
private void loadPaymentURL() {
if (paymentURL != null) {
webView.loadUrl(paymentURL);
}
}
private void handlePaymentSuccess() {
runOnUiThread(() -> {
// Show success message
Toast.makeText(this, "Payment successful!", Toast.LENGTH_LONG).show();
// Return success result
Intent resultIntent = new Intent();
resultIntent.putExtra("payment_status", "success");
setResult(RESULT_OK, resultIntent);
finish();
});
}
private void handlePaymentFailure(String error) {
runOnUiThread(() -> {
// Show error message
Toast.makeText(this, "Payment failed: " + error, Toast.LENGTH_LONG).show();
// Return failure result
Intent resultIntent = new Intent();
resultIntent.putExtra("payment_status", "failed");
resultIntent.putExtra("error", error);
setResult(RESULT_CANCELED, resultIntent);
finish();
});
}
}
// Usage in your main activity
private void openPaymentWebView(String paymentURL) {
Intent intent = new Intent(this, PaymentActivity.class);
intent.putExtra("payment_url", paymentURL);
startActivityForResult(intent, PAYMENT_REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PAYMENT_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
String status = data.getStringExtra("payment_status");
if ("success".equals(status)) {
// Handle successful payment
showSuccessDialog();
refreshOrderStatus();
}
} else {
String error = data.getStringExtra("error");
// Handle payment failure
showErrorDialog(error);
}
}
}
React Native
import React, { useRef } from 'react';
import { WebView } from 'react-native-webview';
import { View, Alert, StyleSheet } from 'react-native';
const PaymentWebView = ({ paymentURL, onPaymentComplete, onPaymentError }) => {
const webViewRef = useRef(null);
const handleNavigationStateChange = (navState) => {
const { url } = navState;
// Check for payment completion URLs
if (url.includes('payment-success')) {
onPaymentComplete();
return;
} else if (url.includes('payment-failed') || url.includes('payment-cancelled')) {
onPaymentError('Payment was cancelled or failed');
return;
}
};
const handleError = (syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
onPaymentError(`WebView error: ${nativeEvent.description}`);
};
const injectedJavaScript = `
// Optional: Inject custom JavaScript for enhanced mobile experience
(function() {
// Add mobile-specific styling or behavior
const meta = document.createElement('meta');
meta.name = 'viewport';
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0';
document.getElementsByTagName('head')[0].appendChild(meta);
})();
true; // Required for iOS
`;
return (
<View style={styles.container}>
<WebView
ref={webViewRef}
source={{ uri: paymentURL }}
style={styles.webView}
onNavigationStateChange={handleNavigationStateChange}
onError={handleError}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
userAgent="YourAppName/1.0 Mobile"
injectedJavaScript={injectedJavaScript}
onShouldStartLoadWithRequest={(request) => {
// Allow all requests by default
return true;
}}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
webView: {
flex: 1,
},
});
// Usage in your component
const CheckoutScreen = () => {
const [paymentURL, setPaymentURL] = useState(null);
const createPaymentLink = async (orderData) => {
try {
const response = await fetch('https://backend.sa.amwal.tech/payment_links/your_store_id/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'your_api_key_here',
},
body: JSON.stringify({
amount: orderData.amount,
title: orderData.title,
description: orderData.description,
singleUse: true,
client_email: orderData.customerEmail,
client_first_name: orderData.customerFirstName,
language: 'en',
send_sms: true,
callback_url: 'https://yourstore.com/webhook/payment-complete',
metadata: {
order_id: orderData.orderId,
platform: 'mobile_app'
}
})
});
const result = await response.json();
setPaymentURL(result.url);
} catch (error) {
Alert.alert('Error', 'Failed to create payment link');
}
};
const handlePaymentComplete = () => {
Alert.alert('Success', 'Payment completed successfully!', [
{
text: 'OK',
onPress: () => {
setPaymentURL(null);
// Navigate to success screen or refresh orders
}
}
]);
};
const handlePaymentError = (error) => {
Alert.alert('Payment Failed', error, [
{
text: 'OK',
onPress: () => setPaymentURL(null)
}
]);
};
if (paymentURL) {
return (
<PaymentWebView
paymentURL={paymentURL}
onPaymentComplete={handlePaymentComplete}
onPaymentError={handlePaymentError}
/>
);
}
// Your regular checkout UI here
return (
<View>
{/* Your checkout form */}
</View>
);
};
export default CheckoutScreen;
Step 3: Handle Payment Completion
Webhook Setup
Set up a webhook endpoint to receive payment notifications and verify status:
// Express.js webhook handler
app.get('/webhook/payment-complete', async (req, res) => {
const { transaction_id, reference_number } = req.query;
if (!transaction_id) {
return res.status(400).send('Missing transaction_id');
}
try {
// Get payment details using the transaction_id
const paymentDetails = await getPaymentDetails(transaction_id);
if (paymentDetails && paymentDetails.payment_link.status === 'Paid') {
// Extract order information from metadata
const metadata = paymentDetails.payment_link.metadata || {};
// Update order status in database
if (metadata.order_id) {
await updateOrderStatus(metadata.order_id, 'paid');
}
// Send confirmation email
if (metadata.customer_id) {
await sendOrderConfirmation(metadata.customer_id);
}
// Update inventory
if (metadata.order_id) {
await updateInventory(metadata.order_id);
}
console.log(`Payment completed for transaction: ${transaction_id}`);
}
res.status(200).send('OK');
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).send('Internal Server Error');
}
});
// Function to get payment details using transaction_id
const getPaymentDetails = async (transactionId) => {
// Note: You'll need to map transaction_id to payment_link_id
// This might require storing the relationship when creating payment links
// or using a different endpoint that accepts transaction_id
const response = await fetch(`https://backend.sa.amwal.tech/payment_links/${paymentLinkId}/details`, {
method: 'POST',
headers: {
'Authorization': 'your_api_key_here',
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Amwal-Key': 'sandbox-amwal-xxx' // For sandbox testing
},
body: JSON.stringify({})
});
return await response.json();
};
Alternative: Store Transaction Mapping
Since the webhook provides transaction_id
but the API needs payment_link_id
, store this mapping when creating payment links:
// When creating payment link, store the mapping
const createPaymentLink = async (orderData) => {
const response = await fetch('https://backend.sa.amwal.tech/payment_links/your_store_id/create', {
// ... headers and body
});
const result = await response.json();
if (response.ok) {
// Store the mapping for webhook processing
await storePaymentMapping({
order_id: orderData.orderId,
payment_link_id: result.payment_link_id,
payment_url: result.url,
created_at: new Date()
});
return result;
}
};
// Updated webhook handler with mapping lookup
app.get('/webhook/payment-complete', async (req, res) => {
const { transaction_id, reference_number } = req.query;
try {
// Find the payment link associated with this transaction
// You might need to search through recent transactions
const paymentMapping = await findPaymentByTransaction(transaction_id);
if (paymentMapping) {
const paymentDetails = await getPaymentDetailsByLinkId(paymentMapping.payment_link_id);
if (paymentDetails.payment_link.status === 'Paid') {
// Process successful payment
await processSuccessfulPayment(paymentMapping.order_id, transaction_id);
}
}
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Error');
}
});
Verify Payment Status
Always verify payment status server-side using the payment link ID:
const verifyPaymentStatus = async (paymentLinkId) => {
const response = await fetch(`https://backend.sa.amwal.tech/payment_links/${paymentLinkId}/details`, {
method: 'POST',
headers: {
'Authorization': 'your_api_key_here',
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Amwal-Key': 'sandbox-amwal-xxx' // For sandbox
},
body: JSON.stringify({})
});
const data = await response.json();
return data.payment_link.status === 'Paid';
};
Step 4: Error Handling
Common Scenarios
const handlePaymentErrors = (error) => {
switch (error.error_code) {
case 'LINK_EXPIRED':
// Create new payment link
showAlert('Payment link expired. Creating new one...');
createNewPaymentLink();
break;
case 'INVALID_AMOUNT':
// Handle amount validation
showAlert('Invalid payment amount. Please try again.');
break;
case 'NETWORK_ERROR':
// Handle network issues
showAlert('Network error. Please check your connection.');
break;
default:
showAlert('Payment failed. Please try again.');
}
};
Best Practices
1. Security
- Always validate payments server-side
- Implement webhook signature verification
- Use HTTPS for all API calls
- Store API keys securely (not in client code)
2. User Experience
- Show loading indicators during API calls
- Handle network timeouts gracefully
- Provide clear error messages
- Allow users to retry failed payments
3. Testing
- Use sandbox environment for development
- Test various payment scenarios
- Test network connectivity issues
- Verify webhook handling
4. Performance
- Cache payment links when appropriate
- Implement proper timeout handling
- Optimize web-view loading
Updated about 3 hours ago