254 lines
6.9 KiB
Dart
254 lines
6.9 KiB
Dart
import 'package:get/get.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'api_service.dart';
|
|
|
|
/// PhonePe Payment Gateway Integration Service
|
|
/// Handles PhonePe payments for chit fund installments
|
|
class PhonePeService extends GetxController {
|
|
static PhonePeService get to => Get.find();
|
|
|
|
final ApiService _apiService = ApiService();
|
|
|
|
// Observable state
|
|
final RxBool _isProcessing = false.obs;
|
|
final RxString _error = ''.obs;
|
|
final RxString _currentTransactionId = ''.obs;
|
|
|
|
// Getters
|
|
bool get isProcessing => _isProcessing.value;
|
|
String get error => _error.value;
|
|
String get currentTransactionId => _currentTransactionId.value;
|
|
|
|
/// Initiate a payment for monthly installment
|
|
Future<Map<String, dynamic>?> initiatePayment({
|
|
required String groupId,
|
|
required String userId,
|
|
required int month,
|
|
required int year,
|
|
required double amount,
|
|
required String userName,
|
|
required String userMobile,
|
|
}) async {
|
|
try {
|
|
_isProcessing.value = true;
|
|
_error.value = '';
|
|
|
|
// Generate unique transaction ID
|
|
final transactionId = 'TXN_${DateTime.now().millisecondsSinceEpoch}';
|
|
_currentTransactionId.value = transactionId;
|
|
|
|
// Create payment request on backend
|
|
final response = await _apiService.post(
|
|
'/payments/phonepe/initiate',
|
|
{
|
|
'group_id': groupId,
|
|
'user_id': userId,
|
|
'month': month,
|
|
'year': year,
|
|
'amount': amount,
|
|
'transaction_id': transactionId,
|
|
'user_name': userName,
|
|
'user_mobile': userMobile,
|
|
},
|
|
);
|
|
|
|
if (response['success'] == true) {
|
|
final paymentData = response['data'];
|
|
|
|
// Launch PhonePe payment
|
|
final result = await _launchPhonePePayment(paymentData);
|
|
|
|
if (result != null && result['success'] == true) {
|
|
// Verify payment on backend
|
|
return await verifyPayment(transactionId);
|
|
} else {
|
|
_error.value = result?['message'] ?? 'Payment failed';
|
|
return null;
|
|
}
|
|
} else {
|
|
_error.value = response['message'] ?? 'Failed to initiate payment';
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
print('PhonePe payment error: $e');
|
|
_error.value = 'Payment failed: $e';
|
|
return null;
|
|
} finally {
|
|
_isProcessing.value = false;
|
|
}
|
|
}
|
|
|
|
/// Launch PhonePe payment flow
|
|
Future<Map<String, dynamic>?> _launchPhonePePayment(
|
|
Map<String, dynamic> paymentData,
|
|
) async {
|
|
try {
|
|
// Use PhonePe SDK or deep link
|
|
final String paymentUrl = paymentData['payment_url'];
|
|
final String checksum = paymentData['checksum'];
|
|
|
|
// Method 1: Using platform channel (if PhonePe SDK is integrated)
|
|
try {
|
|
final result = await _invokePhonePeSDK(
|
|
paymentUrl: paymentUrl,
|
|
checksum: checksum,
|
|
);
|
|
return result;
|
|
} catch (e) {
|
|
print('PhonePe SDK not available: $e');
|
|
|
|
// Method 2: Using URL launcher as fallback
|
|
return await _launchPhonePeUrl(paymentUrl);
|
|
}
|
|
} catch (e) {
|
|
print('Error launching PhonePe: $e');
|
|
return {
|
|
'success': false,
|
|
'message': 'Failed to launch PhonePe: $e',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Invoke PhonePe SDK via platform channel
|
|
Future<Map<String, dynamic>> _invokePhonePeSDK({
|
|
required String paymentUrl,
|
|
required String checksum,
|
|
}) async {
|
|
const platform = MethodChannel('com.luckychit.phonepe');
|
|
|
|
try {
|
|
final result = await platform.invokeMethod('startPayment', {
|
|
'paymentUrl': paymentUrl,
|
|
'checksum': checksum,
|
|
'callbackUrl': 'luckychit://payment/callback',
|
|
});
|
|
|
|
return {
|
|
'success': true,
|
|
'data': result,
|
|
};
|
|
} on PlatformException catch (e) {
|
|
return {
|
|
'success': false,
|
|
'message': e.message ?? 'Payment failed',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Launch PhonePe using URL (fallback method)
|
|
Future<Map<String, dynamic>> _launchPhonePeUrl(String paymentUrl) async {
|
|
// This would use url_launcher in a real implementation
|
|
// For now, return a simulated response
|
|
return {
|
|
'success': true,
|
|
'message': 'Payment URL opened',
|
|
};
|
|
}
|
|
|
|
/// Verify payment status on backend
|
|
Future<Map<String, dynamic>?> verifyPayment(String transactionId) async {
|
|
try {
|
|
final response = await _apiService.post(
|
|
'/payments/phonepe/verify',
|
|
{'transaction_id': transactionId},
|
|
);
|
|
|
|
if (response['success'] == true) {
|
|
return response['data'];
|
|
} else {
|
|
_error.value = response['message'] ?? 'Payment verification failed';
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
print('Payment verification error: $e');
|
|
_error.value = 'Verification failed: $e';
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Check payment status
|
|
Future<String> getPaymentStatus(String transactionId) async {
|
|
try {
|
|
final response = await _apiService.get(
|
|
'/payments/phonepe/status/$transactionId',
|
|
);
|
|
|
|
if (response['success'] == true) {
|
|
return response['data']['status'] ?? 'pending';
|
|
} else {
|
|
return 'failed';
|
|
}
|
|
} catch (e) {
|
|
print('Error checking payment status: $e');
|
|
return 'error';
|
|
}
|
|
}
|
|
|
|
/// Handle payment callback (for deep links)
|
|
Future<void> handlePaymentCallback(Map<String, dynamic> callbackData) async {
|
|
try {
|
|
final transactionId = callbackData['transactionId'];
|
|
final status = callbackData['status'];
|
|
|
|
if (status == 'SUCCESS') {
|
|
// Verify payment
|
|
await verifyPayment(transactionId);
|
|
} else {
|
|
_error.value = 'Payment ${status.toLowerCase()}';
|
|
}
|
|
} catch (e) {
|
|
print('Error handling callback: $e');
|
|
_error.value = 'Failed to process payment callback';
|
|
}
|
|
}
|
|
|
|
/// Initiate refund (for cancelled payments)
|
|
Future<bool> initiateRefund({
|
|
required String transactionId,
|
|
required double amount,
|
|
String? reason,
|
|
}) async {
|
|
try {
|
|
final response = await _apiService.post(
|
|
'/payments/phonepe/refund',
|
|
{
|
|
'transaction_id': transactionId,
|
|
'amount': amount,
|
|
'reason': reason,
|
|
},
|
|
);
|
|
|
|
return response['success'] == true;
|
|
} catch (e) {
|
|
print('Refund error: $e');
|
|
_error.value = 'Refund failed: $e';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Get transaction details
|
|
Future<Map<String, dynamic>?> getTransactionDetails(
|
|
String transactionId,
|
|
) async {
|
|
try {
|
|
final response = await _apiService.get(
|
|
'/payments/phonepe/transaction/$transactionId',
|
|
);
|
|
|
|
if (response['success'] == true) {
|
|
return response['data'];
|
|
}
|
|
return null;
|
|
} catch (e) {
|
|
print('Error fetching transaction: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Clear error state
|
|
void clearError() {
|
|
_error.value = '';
|
|
}
|
|
}
|
|
|