Mobile App Integration
Complete guide for integrating Moneybag payments into mobile applications
Mobile App Integration Guide
This guide walks you through integrating Moneybag payments into your mobile application using Flutter as an example. The same principles apply to native iOS and Android development.
Overview
Mobile app payment integration follows a server-assisted flow for security:
- App initiates payment - Collects order details
- Backend creates checkout - Calls Moneybag API securely
- App opens payment page - Using WebView or browser
- Backend verifies payment - Confirms transaction status
- App shows result - Updates UI based on payment status
Security Notice
Never store API keys in your mobile app. Always use a backend server to communicate with the Moneybag API.
Architecture
sequenceDiagram
participant App as Mobile App
participant Backend as Your Backend
participant Moneybag as Moneybag API
participant WebView as Payment Page
App->>Backend: Request payment (order details)
Backend->>Moneybag: POST /payments/checkout
Moneybag-->>Backend: Return checkout_url
Backend-->>App: Send checkout_url
App->>WebView: Open checkout_url
WebView->>Moneybag: Process payment
Moneybag-->>WebView: Payment complete
WebView-->>App: Redirect with result
App->>Backend: Verify payment
Backend->>Moneybag: GET /payments/verify/{id}
Moneybag-->>Backend: Payment details
Backend-->>App: Confirmed status
Flutter Implementation
Step 1: Setup Dependencies
Add required packages to pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
http: ^1.1.0
webview_flutter: ^4.4.0
url_launcher: ^6.2.0
flutter_dotenv: ^5.1.0
Step 2: Create Payment Service
Create a service to communicate with your backend:
// lib/services/payment_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class PaymentService {
static const String baseUrl = 'https://your-backend.com/api';
// Create checkout session via your backend
static Future<CheckoutResponse> createCheckout({
required double amount,
required String orderId,
required CustomerInfo customer,
}) async {
try {
final response = await http.post(
Uri.parse('$baseUrl/create-checkout'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${await getAuthToken()}',
},
body: jsonEncode({
'order_id': orderId,
'amount': amount,
'currency': 'BDT',
'customer': customer.toJson(),
}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return CheckoutResponse.fromJson(data);
} else {
throw Exception('Failed to create checkout');
}
} catch (e) {
throw Exception('Network error: $e');
}
}
// Verify payment status via your backend
static Future<PaymentStatus> verifyPayment(String transactionId) async {
try {
final response = await http.get(
Uri.parse('$baseUrl/verify-payment/$transactionId'),
headers: {
'Authorization': 'Bearer ${await getAuthToken()}',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return PaymentStatus.fromJson(data);
} else {
throw Exception('Failed to verify payment');
}
} catch (e) {
throw Exception('Verification error: $e');
}
}
static Future<String> getAuthToken() async {
// Implement your auth token retrieval
// This could be from secure storage, login session, etc.
return 'user_auth_token';
}
}
// Data models
class CheckoutResponse {
final String checkoutUrl;
final String transactionId;
final DateTime expiresAt;
CheckoutResponse({
required this.checkoutUrl,
required this.transactionId,
required this.expiresAt,
});
factory CheckoutResponse.fromJson(Map<String, dynamic> json) {
return CheckoutResponse(
checkoutUrl: json['checkout_url'],
transactionId: json['transaction_id'],
expiresAt: DateTime.parse(json['expires_at']),
);
}
}
class CustomerInfo {
final String name;
final String email;
final String phone;
final String address;
final String city;
final String postcode;
final String country;
CustomerInfo({
required this.name,
required this.email,
required this.phone,
required this.address,
required this.city,
required this.postcode,
required this.country,
});
Map<String, dynamic> toJson() {
return {
'name': name,
'email': email,
'phone': phone,
'address': address,
'city': city,
'postcode': postcode,
'country': country,
};
}
}
class PaymentStatus {
final String transactionId;
final String status;
final double amount;
final String paymentMethod;
final DateTime? paidAt;
PaymentStatus({
required this.transactionId,
required this.status,
required this.amount,
this.paymentMethod = '',
this.paidAt,
});
factory PaymentStatus.fromJson(Map<String, dynamic> json) {
return PaymentStatus(
transactionId: json['transaction_id'],
status: json['status'],
amount: json['amount'].toDouble(),
paymentMethod: json['payment_method'] ?? '',
paidAt: json['paid_at'] != null
? DateTime.parse(json['paid_at'])
: null,
);
}
bool get isSuccess => status == 'SUCCESS';
bool get isFailed => status == 'FAILED';
bool get isPending => status == 'PENDING';
}
Step 3: Create Payment WebView
Create a WebView widget to handle the payment page:
// lib/screens/payment_webview.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class PaymentWebView extends StatefulWidget {
final String checkoutUrl;
final String transactionId;
final Function(String) onPaymentComplete;
const PaymentWebView({
Key? key,
required this.checkoutUrl,
required this.transactionId,
required this.onPaymentComplete,
}) : super(key: key);
@override
State<PaymentWebView> createState() => _PaymentWebViewState();
}
class _PaymentWebViewState extends State<PaymentWebView> {
late final WebViewController controller;
bool isLoading = true;
@override
void initState() {
super.initState();
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
setState(() {
isLoading = progress < 100;
});
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
_checkForRedirect(url);
},
onNavigationRequest: (NavigationRequest request) {
// Handle deep links back to your app
if (request.url.startsWith('yourapp://')) {
_handleDeepLink(request.url);
return NavigationDecision.prevent;
}
// Check for success/fail/cancel URLs
if (_isCallbackUrl(request.url)) {
_handleCallback(request.url);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(widget.checkoutUrl));
}
bool _isCallbackUrl(String url) {
// Check if URL matches your callback URLs
return url.contains('/payment/success') ||
url.contains('/payment/fail') ||
url.contains('/payment/cancel');
}
void _handleCallback(String url) {
if (url.contains('/payment/success')) {
// Extract transaction ID from URL if present
final uri = Uri.parse(url);
final transactionId = uri.queryParameters['transaction_id'] ??
widget.transactionId;
widget.onPaymentComplete(transactionId);
Navigator.pop(context, 'success');
} else if (url.contains('/payment/fail')) {
Navigator.pop(context, 'failed');
} else if (url.contains('/payment/cancel')) {
Navigator.pop(context, 'cancelled');
}
}
void _handleDeepLink(String url) {
// Handle deep links back to your app
final uri = Uri.parse(url);
final status = uri.queryParameters['status'];
if (status == 'success') {
widget.onPaymentComplete(widget.transactionId);
Navigator.pop(context, 'success');
} else {
Navigator.pop(context, status);
}
}
void _checkForRedirect(String url) {
// Additional check for redirect URLs
if (_isCallbackUrl(url)) {
_handleCallback(url);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Complete Payment'),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Cancel Payment?'),
content: const Text(
'Are you sure you want to cancel this payment?'
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Continue Payment'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context, 'cancelled');
},
child: const Text('Cancel'),
),
],
),
);
},
),
),
body: Stack(
children: [
WebViewWidget(controller: controller),
if (isLoading)
const Center(
child: CircularProgressIndicator(),
),
],
),
);
}
}
Step 4: Implement Checkout Flow
Create the main checkout flow in your app:
// lib/screens/checkout_screen.dart
import 'package:flutter/material.dart';
import 'payment_webview.dart';
import '../services/payment_service.dart';
class CheckoutScreen extends StatefulWidget {
final double amount;
final String orderId;
const CheckoutScreen({
Key? key,
required this.amount,
required this.orderId,
}) : super(key: key);
@override
State<CheckoutScreen> createState() => _CheckoutScreenState();
}
class _CheckoutScreenState extends State<CheckoutScreen> {
final _formKey = GlobalKey<FormState>();
bool _isProcessing = false;
// Form controllers
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _phoneController = TextEditingController();
final _addressController = TextEditingController();
final _cityController = TextEditingController();
final _postcodeController = TextEditingController();
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_phoneController.dispose();
_addressController.dispose();
_cityController.dispose();
_postcodeController.dispose();
super.dispose();
}
Future<void> _initiatePayment() async {
if (!_formKey.currentState!.validate()) return;
setState(() {
_isProcessing = true;
});
try {
// Create customer info
final customer = CustomerInfo(
name: _nameController.text,
email: _emailController.text,
phone: _phoneController.text,
address: _addressController.text,
city: _cityController.text,
postcode: _postcodeController.text,
country: 'Bangladesh',
);
// Create checkout session
final checkout = await PaymentService.createCheckout(
amount: widget.amount,
orderId: widget.orderId,
customer: customer,
);
if (!mounted) return;
// Open payment WebView
final result = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => PaymentWebView(
checkoutUrl: checkout.checkoutUrl,
transactionId: checkout.transactionId,
onPaymentComplete: (transactionId) async {
// Verify payment after completion
await _verifyPayment(transactionId);
},
),
),
);
// Handle result
if (result == 'success') {
_showSuccessDialog();
} else if (result == 'failed') {
_showErrorDialog('Payment failed. Please try again.');
} else if (result == 'cancelled') {
_showInfoDialog('Payment was cancelled.');
}
} catch (e) {
_showErrorDialog('Error: ${e.toString()}');
} finally {
setState(() {
_isProcessing = false;
});
}
}
Future<void> _verifyPayment(String transactionId) async {
try {
final status = await PaymentService.verifyPayment(transactionId);
if (status.isSuccess) {
// Payment verified successfully
debugPrint('Payment verified: ${status.amount} ${status.paymentMethod}');
} else if (status.isFailed) {
throw Exception('Payment verification failed');
}
} catch (e) {
debugPrint('Verification error: $e');
// Handle verification error
}
}
void _showSuccessDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Row(
children: [
Icon(Icons.check_circle, color: Colors.green),
SizedBox(width: 10),
Text('Payment Successful'),
],
),
content: Text(
'Your payment of ৳${widget.amount.toStringAsFixed(2)} has been processed successfully.'
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context, true); // Return to previous screen
},
child: const Text('OK'),
),
],
),
);
}
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Row(
children: [
Icon(Icons.error, color: Colors.red),
SizedBox(width: 10),
Text('Payment Error'),
],
),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
void _showInfoDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Information'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Checkout'),
),
body: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
// Order Summary
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Order Summary',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Order ID:'),
Text(widget.orderId),
],
),
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Amount:'),
Text(
'৳${widget.amount.toStringAsFixed(2)}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
],
),
),
),
const SizedBox(height: 20),
// Customer Information
const Text(
'Customer Information',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Full Name',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null;
},
),
const SizedBox(height: 10),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
const SizedBox(height: 10),
TextFormField(
controller: _phoneController,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Phone Number',
border: OutlineInputBorder(),
prefixText: '+880 ',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your phone number';
}
return null;
},
),
const SizedBox(height: 10),
TextFormField(
controller: _addressController,
decoration: const InputDecoration(
labelText: 'Address',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your address';
}
return null;
},
),
const SizedBox(height: 10),
TextFormField(
controller: _cityController,
decoration: const InputDecoration(
labelText: 'City',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your city';
}
return null;
},
),
const SizedBox(height: 10),
TextFormField(
controller: _postcodeController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Postcode',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your postcode';
}
return null;
},
),
const SizedBox(height: 30),
// Pay Button
ElevatedButton(
onPressed: _isProcessing ? null : _initiatePayment,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.green,
),
child: _isProcessing
? const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
)
: Text(
'Pay ৳${widget.amount.toStringAsFixed(2)}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}
}
Step 5: Backend Implementation
Your backend server should handle the API calls securely:
// backend/payment.js (Node.js/Express example)
const express = require('express');
const axios = require('axios');
const router = express.Router();
const MONEYBAG_API_KEY = process.env.MONEYBAG_API_KEY;
const MONEYBAG_BASE_URL = 'https://sandbox.api.moneybag.com.bd/api/v2';
// Create checkout session
router.post('/create-checkout', async (req, res) => {
try {
const { order_id, amount, currency, customer } = req.body;
const response = await axios.post(
`${MONEYBAG_BASE_URL}/payments/checkout`,
{
order_id,
order_amount: amount,
currency: currency || 'BDT',
order_description: 'Mobile app purchase',
success_url: 'yourapp://payment/success',
cancel_url: 'yourapp://payment/cancel',
fail_url: 'yourapp://payment/fail',
customer: {
name: customer.name,
email: customer.email,
phone: customer.phone,
address: customer.address,
city: customer.city,
postcode: customer.postcode,
country: customer.country
}
},
{
headers: {
'X-Merchant-API-Key': MONEYBAG_API_KEY,
'Content-Type': 'application/json'
}
}
);
res.json({
checkout_url: response.data.data.checkout_url,
transaction_id: response.data.data.session_id,
expires_at: response.data.data.expires_at
});
} catch (error) {
console.error('Checkout error:', error);
res.status(500).json({ error: 'Failed to create checkout' });
}
});
// Verify payment
router.get('/verify-payment/:transactionId', async (req, res) => {
try {
const { transactionId } = req.params;
const response = await axios.get(
`${MONEYBAG_BASE_URL}/payments/verify/${transactionId}`,
{
headers: {
'X-Merchant-API-Key': MONEYBAG_API_KEY
}
}
);
const data = response.data.data;
res.json({
transaction_id: data.transaction_id,
status: data.status,
amount: data.amount,
payment_method: data.payment_method,
paid_at: data.paid_at
});
} catch (error) {
console.error('Verification error:', error);
res.status(500).json({ error: 'Failed to verify payment' });
}
});
module.exports = router;
Deep Link Configuration
Android Setup
Add deep link configuration to android/app/src/main/AndroidManifest.xml
:
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<!-- Deep link configuration -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="yourapp"
android:host="payment" />
</intent-filter>
</activity>
iOS Setup
Add URL scheme to ios/Runner/Info.plist
:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.yourcompany.yourapp</string>
</dict>
</array>
Alternative: External Browser
For simpler implementation, you can use the device's browser:
import 'package:url_launcher/url_launcher.dart';
Future<void> _openPaymentInBrowser(String checkoutUrl) async {
final Uri url = Uri.parse(checkoutUrl);
if (await canLaunchUrl(url)) {
await launchUrl(
url,
mode: LaunchMode.externalApplication,
);
// Start polling for payment status
_startPollingForPaymentStatus();
} else {
throw Exception('Could not launch payment URL');
}
}
void _startPollingForPaymentStatus() {
Timer.periodic(const Duration(seconds: 5), (timer) async {
try {
final status = await PaymentService.verifyPayment(transactionId);
if (status.isSuccess || status.isFailed) {
timer.cancel();
// Handle payment result
if (status.isSuccess) {
_showSuccessDialog();
} else {
_showErrorDialog('Payment failed');
}
}
} catch (e) {
// Continue polling
}
});
}
Security Best Practices
1. Secure API Communication
// Use HTTPS only
class SecureApiClient {
static const String baseUrl = 'https://your-backend.com/api';
static Future<Map<String, dynamic>> secureRequest(
String endpoint,
Map<String, dynamic> data,
) async {
// Add request signing
final signature = await _generateSignature(data);
final response = await http.post(
Uri.parse('$baseUrl/$endpoint'),
headers: {
'Content-Type': 'application/json',
'X-Signature': signature,
'X-Timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
},
body: jsonEncode(data),
);
return jsonDecode(response.body);
}
static Future<String> _generateSignature(Map<String, dynamic> data) async {
// Implement HMAC-SHA256 signing
// Use a shared secret between app and backend
return 'signature';
}
}
2. Certificate Pinning
// Implement certificate pinning for additional security
import 'dart:io';
class SecureHttpClient {
static HttpClient createHttpClient() {
final client = HttpClient();
client.badCertificateCallback = (cert, host, port) {
// Verify certificate fingerprint
final fingerprint = _getCertificateFingerprint(cert);
return _trustedFingerprints.contains(fingerprint);
};
return client;
}
static final _trustedFingerprints = [
'SHA256:XXXXXX', // Your backend certificate fingerprint
];
}
3. Secure Storage
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorage {
static const _storage = FlutterSecureStorage();
static Future<void> saveAuthToken(String token) async {
await _storage.write(key: 'auth_token', value: token);
}
static Future<String?> getAuthToken() async {
return await _storage.read(key: 'auth_token');
}
static Future<void> clearAuthData() async {
await _storage.deleteAll();
}
}
Testing
Test Cards for Mobile
Use these test cards in sandbox:
class TestData {
static const testCards = {
'success': '4111111111111111',
'declined': '4000000000000002',
'3d_secure': '4000000000003220',
};
static const testMobileBanking = {
'bkash': '01700000001',
'nagad': '01800000001',
'rocket': '01900000001',
};
}
Debug Mode
Add debug logging for development:
class PaymentDebugger {
static bool debugMode = kDebugMode;
static void log(String message, [dynamic data]) {
if (debugMode) {
print('[PAYMENT] $message');
if (data != null) {
print('[PAYMENT DATA] ${jsonEncode(data)}');
}
}
}
static void logError(String message, dynamic error) {
if (debugMode) {
print('[PAYMENT ERROR] $message');
print('[ERROR DETAILS] $error');
}
}
}
Common Issues & Solutions
WebView Not Loading
// Ensure JavaScript is enabled
controller.setJavaScriptMode(JavaScriptMode.unrestricted);
// Add user agent for better compatibility
controller.setUserAgent('MoneybagMobileApp/1.0 Flutter');
Deep Links Not Working
// Handle both custom scheme and universal links
if (Platform.isIOS) {
// iOS specific handling
if (url.startsWith('yourapp://') ||
url.startsWith('https://yourapp.com/payment')) {
_handleDeepLink(url);
}
} else {
// Android handling
if (url.startsWith('yourapp://')) {
_handleDeepLink(url);
}
}
Payment Status Not Updating
// Implement retry logic with exponential backoff
Future<PaymentStatus> verifyWithRetry(
String transactionId, {
int maxRetries = 3,
}) async {
int retryCount = 0;
while (retryCount < maxRetries) {
try {
return await PaymentService.verifyPayment(transactionId);
} catch (e) {
retryCount++;
if (retryCount >= maxRetries) rethrow;
// Exponential backoff
await Future.delayed(Duration(seconds: pow(2, retryCount).toInt()));
}
}
throw Exception('Max retries exceeded');
}
Performance Optimization
1. Preload WebView
class WebViewPreloader {
static WebViewController? _preloadedController;
static void preload() {
_preloadedController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(Uri.parse('about:blank'));
}
static WebViewController getController() {
return _preloadedController ?? WebViewController();
}
}
2. Cache Customer Data
class CustomerDataCache {
static Future<CustomerInfo?> getCachedCustomer() async {
final prefs = await SharedPreferences.getInstance();
final data = prefs.getString('customer_data');
if (data != null) {
return CustomerInfo.fromJson(jsonDecode(data));
}
return null;
}
static Future<void> cacheCustomer(CustomerInfo customer) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('customer_data', jsonEncode(customer.toJson()));
}
}
Native Platform Examples
iOS (Swift)
// PaymentService.swift
import Foundation
class PaymentService {
static let shared = PaymentService()
private let baseURL = "https://your-backend.com/api"
func createCheckout(amount: Double, orderId: String, customer: CustomerInfo,
completion: @escaping (Result<CheckoutResponse, Error>) -> Void) {
let url = URL(string: "\(baseURL)/create-checkout")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body = [
"order_id": orderId,
"amount": amount,
"customer": customer.toDictionary()
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request) { data, response, error in
// Handle response
}.resume()
}
}
Android (Kotlin)
// PaymentService.kt
package com.yourapp.payment
import retrofit2.http.*
interface PaymentApi {
@POST("create-checkout")
suspend fun createCheckout(
@Body request: CheckoutRequest
): CheckoutResponse
@GET("verify-payment/{transactionId}")
suspend fun verifyPayment(
@Path("transactionId") transactionId: String
): PaymentStatus
}
class PaymentService(private val api: PaymentApi) {
suspend fun initiatePayment(
amount: Double,
orderId: String,
customer: CustomerInfo
): CheckoutResponse {
return api.createCheckout(
CheckoutRequest(
orderId = orderId,
amount = amount,
customer = customer
)
)
}
}
Support
Need help with mobile integration?
- Documentation: API Reference
- Flutter SDK: Coming Soon
- Support Email: mobile@moneybag.com.bd
- Sample Apps: GitHub Examples