diff --git a/.env b/.env index a168c63..f89f769 100644 --- a/.env +++ b/.env @@ -12,7 +12,7 @@ chatGPTkeySeferNew=zg-Z4AJcAROgNXjgrEIU8fKC9XrxgUE4Qtrrlq1yiux0jL3dITSXrXlBl secretKey=zg_ropj_57Iiv6MFCBFq3C2n6IXlmjykpxDmW93SW3vvXh68UA9T5FORTWgWsT37StKsOPdwDdsy8qR9srMUluahs3nPHvgBa33tGk90vV5XrXlBl stripe_publishableKe=vg_ropj_57Iiv6MFCBFq3C2n6kNJnZByV6nuDtXe9IjEPOfhmpDtWmt3MLR0gQpiHcQmAFMUPrZc3QiCDjxBZLbxDC3efxWxz33bWH1ZgrsXrXlBl llamaKey=RR-EuyoFDUvfRDBj46fZKAtKJ3voM8Mt768cPeJV7GNdAkPTKdY8Odm9n4ggGqI5GyoXrXlBl -serverPHP=https://ride.mobile-app.store +serverPHP=https://api.sefer.live/sefer/ cohere=Aulwd8y5SPWos0hJhG0toUf8gOhUUrpf5Q2TPmVGXrXlBl claudeAiAPI=zg-qbc-qvo39-xWOxIGwWTOzCFBnIYSKKhfyz_KVAvrH-6_4ZEJL68G_QBH26oeTOMMoQug9KuOjjKSP_A4S3SUDlbxR9duVzoQ-MkX_UQQQXrXlBl payPalClientId=QALymfNI5Tzt4s-ysoz6vD4_nqX0SUtkC_qYV-Ugk5gaM_8Z-kg4L53k8Uux_4jEWXDkNpXGSWPpIzDFXrXlBl diff --git a/lib/constant/links.dart b/lib/constant/links.dart index 377c0ce..e98334b 100644 --- a/lib/constant/links.dart +++ b/lib/constant/links.dart @@ -1,7 +1,7 @@ import 'package:SEFER/env/env.dart'; class AppLink { - static final String server = Env.serverPHP; + static final String server = 'https://api.sefer.live/sefer'; // Env.serverPHP; static String googleMapsLink = 'https://maps.googleapis.com/maps/api/'; static String llama = 'https://api.llama-api.com/chat/completions'; static String gemini = @@ -172,7 +172,7 @@ class AppLink { //===================Auth============ - static String auth = 'https://ride.mobile-app.store/auth'; + static String auth = '$server/auth'; static String login = "$auth/login.php"; static String signUp = "$auth/signup.php"; static String sendVerifyEmail = "$auth/sendVerifyEmail.php"; @@ -180,7 +180,7 @@ class AppLink { "$auth/passengerRemovedAccountEmail.php"; static String verifyEmail = "$auth/verifyEmail.php"; //===================Auth Captin============ - static String authCaptin = 'https://ride.mobile-app.store/auth/captin'; + static String authCaptin = '$server/auth/captin'; static String loginCaptin = "$authCaptin/login.php"; static String signUpCaptin = "$authCaptin/register.php"; static String sendVerifyEmailCaptin = "$authCaptin/sendVerifyEmail.php"; diff --git a/lib/controller/auth/login_controller.dart b/lib/controller/auth/login_controller.dart index 2466390..3fcbe4e 100644 --- a/lib/controller/auth/login_controller.dart +++ b/lib/controller/auth/login_controller.dart @@ -84,17 +84,18 @@ class LoginController extends GetxController { 'phone': phoneController.text, 'password': passwordController.text }); + isloading = false; + update(); if (res == 'Failure') { - isloading = false; - update(); Get.snackbar('Failure', '', backgroundColor: Colors.red); } else { + // print(res); var jsonDecoeded = jsonDecode(res); // print(jsonDecoeded); if (jsonDecoeded.isNotEmpty) { if (jsonDecoeded['status'] == 'success') { print(jsonDecoeded['data'][0]['verified']); - if (jsonDecoeded['data'][0]['verified'] == 1) { + if (jsonDecoeded['data'][0]['verified'] == '1') { box.write(BoxName.passengerID, jsonDecoeded['data'][0]['id']); box.write(BoxName.email, jsonDecoeded['data'][0]['email']); box.write( diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart index 03aa85f..56bdcd4 100644 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -12,8 +12,6 @@ class CRUD { required String link, Map? payload, }) async { - // String? basicAuthCredentials = - // await storage.read(key: BoxName.basicAuthCredentials); var url = Uri.parse( link, ); @@ -23,26 +21,24 @@ class CRUD { headers: { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': - // 'Basic ${base64Encode(utf8.encode('hamzaayedphp:malDEV@2101'))}', 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}', - // 'Basic ${base64Encode(utf8.encode(basicAuthCredentials.toString()))}', }, ); - print("-----request----" + response.request.toString()); + // print("-----request----" + response.request.toString()); // print("-----headers-----" + response.headers.toString()); - print("-----payload-----" + payload.toString()); - if (response.statusCode == 200) { - // print(response.body); - var jsonData = jsonDecode(response.body); - if (jsonData['status'] == 'success') { - print(jsonData); + // print("-----payload-----" + payload.toString()); + // if (response.statusCode == 200) { + // print(response.body); + var jsonData = jsonDecode(response.body); + if (jsonData['status'] == 'success') { + // print(jsonData); - return response.body; - } - - return jsonData['status']; + return response.body; } + + return jsonData['status']; } + // } Future getAgoraToken({ required String channelName, @@ -174,8 +170,8 @@ class CRUD { 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', }, ); - print(response.request); - print(payload); + // print(response.request); + // print(payload); var jsonData = jsonDecode(response.body); // print(jsonData); diff --git a/lib/controller/functions/location_controller.dart b/lib/controller/functions/location_controller.dart index c217c17..c916f1d 100644 --- a/lib/controller/functions/location_controller.dart +++ b/lib/controller/functions/location_controller.dart @@ -164,18 +164,18 @@ class LocationController extends GetxController { print('distance $distance'); totalDistance += distance; } - print('totalDistance: $totalDistance'); + // print('totalDistance: $totalDistance'); previousTime = _locationData.time!; } // Print location details - print('myLocation: ${myLocation}'); - print('Accuracy: ${_locationData.accuracy}'); - print('Latitude: ${_locationData.latitude}'); - print('Longitude: ${_locationData.longitude}'); - print('Time: ${_locationData.time}'); - print('speed: ${_locationData.speed}'); - print('Heading: ${_locationData.heading}'); + // print('myLocation: ${myLocation}'); + // print('Accuracy: ${_locationData.accuracy}'); + // print('Latitude: ${_locationData.latitude}'); + // print('Longitude: ${_locationData.longitude}'); + // print('Time: ${_locationData.time}'); + // print('speed: ${_locationData.speed}'); + // print('Heading: ${_locationData.heading}'); // isLoading = false; update(); } diff --git a/lib/controller/functions/log_out.dart b/lib/controller/functions/log_out.dart index 9c17e52..71bac58 100644 --- a/lib/controller/functions/log_out.dart +++ b/lib/controller/functions/log_out.dart @@ -107,6 +107,7 @@ class LogOutController extends GetxController { box.remove(BoxName.apiKeyRun); box.remove(BoxName.countryCode); box.remove(BoxName.accountIdStripeConnect); + box.remove(BoxName.passengerWalletTotal); Get.offAll(OnBoardingPage()); }, child: Text( diff --git a/lib/controller/local/translations.dart b/lib/controller/local/translations.dart index 505091e..a0ad144 100644 --- a/lib/controller/local/translations.dart +++ b/lib/controller/local/translations.dart @@ -6,6 +6,9 @@ class MyTranslation extends Translations { "ar": { "Choose Language": "اختر اللغة", "Login": "تسجيل الدخول", + 'Pay with Wallet': 'ادفع باستخدام المحفظة', + 'Invalid MPIN': 'رمز PIN غير صحيح', + 'Invalid OTP': 'كود التحقق خاطئ', "Enter your email address": "أدخل عنوان بريدك الإلكتروني", "Please enter Your Email.": "يرجى إدخال بريدك الإلكتروني.", "Enter your phone number": "أدخل رقم هاتفك", diff --git a/lib/controller/payment/payment_controller.dart b/lib/controller/payment/payment_controller.dart index e1b092b..969c163 100644 --- a/lib/controller/payment/payment_controller.dart +++ b/lib/controller/payment/payment_controller.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'package:SEFER/constant/api_key.dart'; import 'package:SEFER/constant/style.dart'; +import 'package:SEFER/controller/functions/tts.dart'; +import 'package:SEFER/controller/payment/paymob/paymob_response.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; @@ -9,7 +11,6 @@ import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:get/get.dart'; import 'package:local_auth/local_auth.dart'; import 'package:SEFER/controller/home/map_passenger_controller.dart'; -import 'package:paymob_payment/paymob_payment.dart'; import '../../constant/box_name.dart'; import '../../constant/colors.dart'; @@ -18,6 +19,7 @@ import '../../constant/links.dart'; import '../../main.dart'; import '../functions/crud.dart'; import '../functions/toast.dart'; +import 'paymob/paymob_wallet.dart'; class PaymentController extends GetxController { bool isLoading = false; @@ -55,7 +57,6 @@ class PaymentController extends GetxController { box.write(BoxName.passengerWalletTotal, jsonDecode(value)['message'][0]['total'].toString()); }); - isLoading = false; update(); } @@ -479,11 +480,17 @@ class PaymentController extends GetxController { context: context, currency: currency, //"EGP", amountInCents: newAmount, // 19.00 EGP + + billingData: PaymobBillingData(), onPayment: (PaymobResponse response) { print('Success: ${response.success}'); print('Transaction ID: ${response.transactionID}'); print('Response Code: ${response.responseCode}'); // print('Message: ${response.message}'); + print(box.read(BoxName.passengerWalletTotal)); + print(box.read(BoxName.name)); + print(box.read(BoxName.phone)); + // print(); }, ); @@ -533,6 +540,178 @@ class PaymentController extends GetxController { context: context, currency: currency, //"EGP", amountInCents: newAmount, // 19.00 EGP + billingData: PaymobBillingData(), + onPayment: (PaymobResponse response) { + // print('Success: ${response.success}'); + // print('Transaction ID: ${response.transactionID}'); + // print('Response Code: ${response.responseCode}'); + // print('Message: ${response.message}'); + }, + ); + + if (response!.responseCode == 'APPROVED') { + Get.defaultDialog( + barrierDismissible: false, + title: 'Payment Successful'.tr, + titleStyle: AppStyle.title, + // backgroundColor: AppColor.greenColor, + content: Text( + 'The payment was approved.'.tr, + style: AppStyle.title, + ), + confirm: MyElevatedButton( + kolor: AppColor.greenColor, + title: 'OK'.tr, + onPressed: () async { + Get.back(); + method(); + }, + ), + ); + } else { + Get.defaultDialog( + barrierDismissible: false, + // backgroundColor: AppColor.redColor, + title: 'Payment Failed'.tr, + content: Column( + children: [ + IconButton( + onPressed: () { + Get.find().speakText( + 'The payment was not approved. Please try again.'.tr, + ); + }, + icon: const Icon(Icons.headphones), + ), + Text( + 'The payment was not approved. Please try again.'.tr, + textAlign: TextAlign.center, + style: AppStyle.title, + ), + Text( + '${'The reason is'.tr} ${response.message!.tr}', + textAlign: TextAlign.center, + style: AppStyle.title.copyWith(color: AppColor.redColor), + ), + ], + ), + confirm: MyElevatedButton( + title: 'OK'.tr, + kolor: AppColor.redColor, + onPressed: () async { + Get.back(); + }, + ), + ); + } + } + } catch (e) { + Get.defaultDialog( + title: 'Error'.tr, + content: Text( + 'An error occurred during the payment process.'.tr, + style: AppStyle.title, + ), + ); + rethrow; + } + } + + Future payWithPayMobWallet( + BuildContext context, String amount, currency, Function method) async { + String newAmount = (double.parse(amount) * 100).toStringAsFixed(2); + try { + bool isAvailable = await LocalAuthentication().isDeviceSupported(); + if (isAvailable) { + // Authenticate the user + bool didAuthenticate = await LocalAuthentication().authenticate( + localizedReason: 'Use Touch ID or Face ID to confirm payment', + ); + if (didAuthenticate) { + final PaymobResponseWallet? response = + await PaymobPaymentWallet.instance.pay( + context: context, + currency: currency, //"EGP", + amountInCents: newAmount, // 19.00 EGP + + billingData: PaymobBillingDataWallet(), + onPayment: (PaymobResponseWallet response) { + print('Success: ${response.success}'); + print('Transaction ID: ${response.transactionID}'); + print('Response Code: ${response.responseCode}'); + print('Message: ${response.message}'); + + // print(box.read(BoxName.passengerWalletTotal));// + // print(box.read(BoxName.name)); + // print(box.read(BoxName.phone)); + // print(); + }, + ); + + if (response!.success == true && response.responseCode == '200') { + Get.defaultDialog( + barrierDismissible: false, + title: 'Payment Successful'.tr, + titleStyle: AppStyle.title, + content: Text( + 'The payment was approved.'.tr, + style: AppStyle.title, + ), + confirm: MyElevatedButton( + title: 'OK'.tr, + kolor: AppColor.greenColor, + onPressed: () async { + Get.back(); + method(); + }, + ), + ); + } else { + Get.defaultDialog( + barrierDismissible: false, + // backgroundColor: AppColor.redColor, + title: 'Payment Failed'.tr, + content: Column( + children: [ + IconButton( + onPressed: () { + Get.find().speakText( + 'The payment was not approved. Please try again.'.tr, + ); + }, + icon: const Icon(Icons.headphones), + ), + Text( + 'The payment was not approved. Please try again.'.tr, + textAlign: TextAlign.center, + style: AppStyle.title, + ), + Text( + '${'The reason is'.tr} ${response.message!.tr}', + textAlign: TextAlign.center, + style: AppStyle.title.copyWith(color: AppColor.redColor), + ), + ], + ), + confirm: MyElevatedButton( + title: 'OK'.tr, + kolor: AppColor.redColor, + onPressed: () async { + Get.back(); + }, + ), + ); + } + } else { + // Authentication failed, handle accordingly + print('Authentication failed'); + } + } else { + final PaymobResponse? response = await PaymobPayment.instance.pay( + context: context, + currency: currency, //"EGP", + amountInCents: newAmount, // 19.00 EGP + billingData: PaymobBillingData(), onPayment: (PaymobResponse response) { // print('Success: ${response.success}'); // print('Transaction ID: ${response.transactionID}'); diff --git a/lib/controller/payment/paymob.dart b/lib/controller/payment/paymob.dart index 0947351..4cb86b8 100644 --- a/lib/controller/payment/paymob.dart +++ b/lib/controller/payment/paymob.dart @@ -1,6 +1,7 @@ import 'package:SEFER/constant/box_name.dart'; import 'package:dio/dio.dart' as dio; import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -62,16 +63,18 @@ class PaymobManager extends GetxController { final dio = Dio(); try { final response = await dio.post( - 'https://accept.paymob.com/api/acceptance/payments/pay', + 'https://accept.paymobsolutions.com/api/acceptance/payments/pay', data: data, ); // 4. Handle Payment Response if (response.statusCode == 200) { - // Payment successful: Process response data (e.g., transaction ID) final paymentData = response.data; // Assuming JSON response - print("Payment successful: $paymentData"); + + print("redirection_url: ${paymentData['iframe_redirection_url']}"); + // Navigate to success screen or display success message + launchUrl(Uri.parse(paymentData['iframe_redirection_url'])); } else { // Payment failed: Handle errors (e.g., display error message) print("Payment failed: ${response.statusCode} - ${response.data}"); @@ -82,24 +85,6 @@ class PaymobManager extends GetxController { } } - // Future payWithPayMob(int amount, String currency) async { - // String key = await PaymobManager().getPaymentKey(amount, currency); - // await launchUrl(Uri.parse( - // // 'https://accept.paymob.com/api/acceptance/iframes/837992?payment_token=$key'), - // 'https://accept.paymob.com/api/acceptance/payments/pay')); - // print(key); - // final dio.Response response = await Dio() - // .post('https://accept.paymob.com/api/acceptance/payments/pay', data: { - // "source": { - // "identifier": "01010101010", - // "subtype": "WALLET", - // }, - // "payment_token": key, // token obtained in step 3 - // }); - // - // // String paymentStatus = await _getStatusAfterPaid(); - // } - Future _getStatusAfterPaid() async { print(authanticationToken1); print(orderId1); @@ -154,7 +139,8 @@ class PaymobManager extends GetxController { "expiration": 200, "auth_token": authanticationToken.toString(), "order_id": orderId.toString(), - "integration_id": int.parse(AK.integrationIdPayMob), + "integration_id": + 4556056, ////todo wallet or online card int.parse(AK.integrationIdPayMob), "lock_order_when_paid": "false", "amount_cents": amount, "currency": currency, diff --git a/lib/controller/payment/paymob/paymob_response.dart b/lib/controller/payment/paymob/paymob_response.dart new file mode 100644 index 0000000..9b68ac5 --- /dev/null +++ b/lib/controller/payment/paymob/paymob_response.dart @@ -0,0 +1,328 @@ +import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/main.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class PaymobResponse { + bool success; + String? transactionID; + String? responseCode; + String? message; + + PaymobResponse({ + this.transactionID, + required this.success, + this.responseCode, + this.message, + }); + + factory PaymobResponse.fromJson(Map json) { + return PaymobResponse( + success: json['success'] == 'true', + transactionID: json['id'], + message: json['message'], + responseCode: json['txn_response_code'], + ); + } +} + +class PaymobPayment { + static PaymobPayment instance = PaymobPayment(); + + bool _isInitialized = false; + + final Dio _dio = Dio(); + final _baseURL = 'https://accept.paymob.com/api/'; + late String _apiKey; + late int _integrationID; + late int _iFrameID; + late String _iFrameURL; + late int _userTokenExpiration; + + /// Initializing PaymobPayment instance. + Future initialize({ + /// It is a unique identifier for the merchant which used to authenticate your requests calling any of Accept's API. + /// from dashboard Select Settings -> Account Info -> API Key + required String apiKey, + + /// from dashboard Select Developers -> Payment Integrations -> Online Card ID + required int integrationID, + + /// from paymob Select Developers -> iframes + required int iFrameID, + + /// The expiration time of this payment token in seconds. (The maximum is 3600 seconds which is an hour) + int userTokenExpiration = 300, + }) async { + if (_isInitialized) { + return true; + } + _dio.options.baseUrl = _baseURL; + _dio.options.validateStatus = (status) => true; + _apiKey = apiKey; + _integrationID = integrationID; + _iFrameID = iFrameID; + _iFrameURL = + 'https://accept.paymobsolutions.com/api/acceptance/iframes/$_iFrameID?payment_token='; + _isInitialized = true; + _userTokenExpiration = userTokenExpiration; + return _isInitialized; + } + + /// Get authentication token, which is valid for one hour from the creation time. + Future _getAuthToken() async { + try { + final response = await _dio.post( + 'auth/tokens', + data: { + 'api_key': _apiKey, + }, + ); + print(response.data['token']); + + return response.data['token']; + } catch (e) { + rethrow; + } + } + + /// At this step, you will register an order to Accept's database, so that you can pay for it later using a transaction + Future _addOrder({ + required String authToken, + required String currency, + required String amount, + required List items, + }) async { + try { + final response = await _dio.post( + 'ecommerce/orders', + data: { + "auth_token": authToken, + "delivery_needed": "false", + "amount_cents": amount, + "currency": currency, + "items": items, + }, + ); + print(response.data['id']); + + return response.data['id']; + } catch (e) { + rethrow; + } + } + + /// At this step, you will obtain a payment_key token. This key will be used to authenticate your payment request. It will be also used for verifying your transaction request metadata. + Future _getPurchaseToken({ + required String authToken, + required String currency, + required int orderID, + required String amount, + required PaymobBillingData billingData, + }) async { + final response = await _dio.post( + 'acceptance/payment_keys', + data: { + "auth_token": authToken, + "amount_cents": amount, + "expiration": _userTokenExpiration, + "order_id": orderID, + "billing_data": billingData, + "currency": currency, + "integration_id": _integrationID, + "lock_order_when_paid": "false" + }, + ); + final message = response.data['message']; + if (message != null) { + throw Exception(message); + } + print(response.data['token']); + return response.data['token']; + } + + /// Proceed to pay with only calling this function. + /// Opens a WebView at Paymob redirectedURL to accept user payment info. + Future pay( + { + /// BuildContext for navigation to WebView + required BuildContext context, + + /// Which Currency you would pay in. + required String currency, + + /// Payment amount in cents EX: 20000 is an 200 EGP + required String amountInCents, + + /// Optional Callback if you can use return result of pay function or use this callback + void Function(PaymobResponse response)? onPayment, + + /// list of json objects contains the contents of the purchase. + List? items, + + /// The billing data related to the customer related to this payment. + PaymobBillingData? billingData}) async { + if (!_isInitialized) { + throw Exception( + 'PaymobPayment is not initialized call:`PaymobPayment.instance.initialize`'); + } + final authToken = await _getAuthToken(); + final orderID = await _addOrder( + authToken: authToken, + currency: currency, + amount: amountInCents, + items: items ?? [], + ); + final purchaseToken = await _getPurchaseToken( + authToken: authToken, + currency: currency, + orderID: orderID, + amount: amountInCents, + billingData: billingData ?? PaymobBillingData(), + ); + if (context.mounted) { + final response = await PaymobIFrame.show( + context: context, + redirectURL: _iFrameURL + purchaseToken, + onPayment: onPayment, + ); + return response; + } + return null; + } +} + +class PaymobBillingData { + String? email; + String? firstName; + String? lastName; + String? phoneNumber; + String? apartment; + String? floor; + String? street; + String? building; + String? postalCode; + String? city; + String? state; + String? country; + String? shippingMethod; + + PaymobBillingData({ + this.email, + this.firstName, + this.lastName, + this.phoneNumber, + this.apartment, + this.floor, + this.street, + this.building, + this.postalCode, + this.city, + this.state, + this.country, + this.shippingMethod, + }); + + Map toJson() { + return { + "email": box.read(BoxName.email) ?? box.read(BoxName.emailDriver), + "first_name": box.read(BoxName.name) ?? box.read(BoxName.nameDriver), + "last_name": box.read(BoxName.lastNameDriver) ?? box.read(BoxName.name), + "phone_number": box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver), + "apartment": apartment ?? "NA", + "floor": floor ?? "NA", + "building": building ?? "NA", + "street": street ?? "NA", + "postal_code": postalCode ?? "NA", + "city": city ?? "NA", + "state": state ?? "NA", + "country": country ?? "NA", + "shipping_method": shippingMethod ?? "NA", + }; + } +} + +class PaymobIFrame extends StatefulWidget { + const PaymobIFrame({ + Key? key, + required this.redirectURL, + this.onPayment, + }) : super(key: key); + + final String redirectURL; + final void Function(PaymobResponse)? onPayment; + + static Future show({ + required BuildContext context, + required String redirectURL, + void Function(PaymobResponse)? onPayment, + }) => + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return PaymobIFrame( + onPayment: onPayment, + redirectURL: redirectURL, + ); + }, + ), + ); + + @override + State createState() => _PaymobIFrameState(); +} + +class _PaymobIFrameState extends State { + WebViewController? controller; + + @override + void initState() { + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (NavigationRequest request) { + if (request.url.contains('txn_response_code') && + request.url.contains('success') && + request.url.contains('id')) { + final params = _getParamFromURL(request.url); + final response = PaymobResponse.fromJson(params); + if (widget.onPayment != null) { + widget.onPayment!(response); + } + Navigator.pop(context, response); + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse(widget.redirectURL)); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: controller == null + ? const Center( + child: CircularProgressIndicator.adaptive(), + ) + : SafeArea( + child: WebViewWidget( + controller: controller!, + ), + ), + ); + } + + Map _getParamFromURL(String url) { + final uri = Uri.parse(url); + Map data = {}; + uri.queryParameters.forEach((key, value) { + data[key] = value; + }); + return data; + } +} diff --git a/lib/controller/payment/paymob/paymob_wallet.dart b/lib/controller/payment/paymob/paymob_wallet.dart new file mode 100644 index 0000000..28f5785 --- /dev/null +++ b/lib/controller/payment/paymob/paymob_wallet.dart @@ -0,0 +1,381 @@ +import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/main.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class PaymobResponseWallet { + final bool success; + final String? transactionID; + final String? responseCode; + final String? message; + + PaymobResponseWallet({ + required this.success, + this.transactionID, + this.responseCode, + this.message, + }); + + factory PaymobResponseWallet.fromJson(Map json) { + return PaymobResponseWallet( + success: json['success'] == 'true', + transactionID: json['id'], + responseCode: json['txn_response_code'], + message: json['data']['message'], + ); + } +} + +class PaymobPaymentWallet { + static PaymobPaymentWallet instance = PaymobPaymentWallet(); + + bool _isInitialized = false; + + final Dio _dio = Dio(); + final _baseURL = 'https://accept.paymob.com/api/'; + late String _apiKey; + late int _integrationID; + late int _iFrameID; + late String _iFrameURL; + late int _userTokenExpiration; + + /// Initializing PaymobPayment instance. + Future initialize({ + /// It is a unique identifier for the merchant which used to authenticate your requests calling any of Accept's API. + /// from dashboard Select Settings -> Account Info -> API Key + required String apiKey, + + /// from dashboard Select Developers -> Payment Integrations -> Online Card ID + required int integrationID, + + /// from paymob Select Developers -> iframes + required int iFrameID, + + /// The expiration time of this payment token in seconds. (The maximum is 3600 seconds which is an hour) + int userTokenExpiration = 300, + }) async { + if (_isInitialized) { + return true; + } + _dio.options.baseUrl = _baseURL; + _dio.options.validateStatus = (status) => true; + _apiKey = apiKey; + _integrationID = integrationID; + _iFrameID = iFrameID; + _iFrameURL = + 'https://accept.paymobsolutions.com/api/acceptance/iframes/$_iFrameID?payment_token='; + _isInitialized = true; + _userTokenExpiration = userTokenExpiration; + return _isInitialized; + } + + /// Get authentication token, which is valid for one hour from the creation time. + Future _getAuthToken() async { + try { + final response = await _dio.post( + 'auth/tokens', + data: { + 'api_key': _apiKey, + }, + ); + print(response.data['token']); + return response.data['token']; + } catch (e) { + rethrow; + } + } + + /// At this step, you will register an order to Accept's database, so that you can pay for it later using a transaction + Future _addOrder({ + required String authToken, + required String currency, + required String amount, + required List items, + }) async { + try { + final response = await _dio.post( + 'ecommerce/orders', + data: { + "auth_token": authToken, + "delivery_needed": "false", + "amount_cents": amount, + "currency": currency, + "items": items, + }, + ); + print(response.data['id']); + return response.data['id']; + } catch (e) { + rethrow; + } + } + + /// At this step, you will obtain a payment_key token. This key will be used to authenticate your payment request. It will be also used for verifying your transaction request metadata. + Future _getPurchaseToken({ + required String authToken, + required String currency, + required int orderID, + required String amount, + required PaymobBillingDataWallet billingData, + }) async { + final response = await _dio.post( + 'acceptance/payment_keys', + data: { + "auth_token": authToken, + "amount_cents": amount, + "expiration": _userTokenExpiration, + "order_id": orderID, + "billing_data": billingData, + "currency": currency, + "integration_id": _integrationID, + "lock_order_when_paid": "false" + }, + ); + final message = response.data['message']; + if (message != null) { + throw Exception(message); + } + print(response.data['token']); + return response.data['token']; + } + + Future _getWalletUrl({ + required String paymentToken, + }) async { + final Map data = { + "source": { + "identifier": "01010101010", // Replace with actual source identifier + "subtype": "WALLET", + }, + "payment_token": paymentToken, + }; + final dio = Dio(); + try { + final response = await dio.post( + 'https://accept.paymobsolutions.com/api/acceptance/payments/pay', + data: data, + ); + + // 4. Handle Payment Response + if (response.statusCode == 200) { + final paymentData = response.data; // Assuming JSON response + + print("redirection_url: ${paymentData['iframe_redirection_url']}"); + return paymentData['iframe_redirection_url']; + // Navigate to success screen or display success message + } else { + // Payment failed: Handle errors (e.g., display error message) + print("Payment failed: ${response.statusCode} - ${response.data}"); + } + } on DioError catch (e) { + // Handle network or Dio-related errors + print("Error making payment request: $e"); + } + return ''; + } + + /// Proceed to pay with only calling this function. + /// Opens a WebView at Paymob redirectedURL to accept user payment info. + Future pay( + { + /// BuildContext for navigation to WebView + required BuildContext context, + + /// Which Currency you would pay in. + required String currency, + + /// Payment amount in cents EX: 20000 is an 200 EGP + required String amountInCents, + + /// Optional Callback if you can use return result of pay function or use this callback + void Function(PaymobResponseWallet response)? onPayment, + + /// list of json objects contains the contents of the purchase. + List? items, + + /// The billing data related to the customer related to this payment. + PaymobBillingDataWallet? billingData}) async { + if (!_isInitialized) { + throw Exception( + 'PaymobPayment is not initialized call:`PaymobPayment.instance.initialize`'); + } + final authToken = await _getAuthToken(); + final orderID = await _addOrder( + authToken: authToken, + currency: currency, + amount: amountInCents, + items: items ?? [], + ); + final purchaseToken = await _getPurchaseToken( + authToken: authToken, + currency: currency, + orderID: orderID, + amount: amountInCents, + billingData: billingData ?? + PaymobBillingDataWallet( + // email: box.read(BoxName.email) ?? box.read(BoxName.emailDriver), + // firstName: box.read(BoxName.name) ?? box.read(BoxName.nameDriver), + // lastName: + // box.read(BoxName.lastNameDriver) ?? box.read(BoxName.name), + // phoneNumber: + // box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver), + ), + ); + final urlWallet = await _getWalletUrl(paymentToken: purchaseToken); + + if (context.mounted) { + final response = await PaymobIFrameWallet.show( + context: context, + redirectURL: urlWallet, + onPayment: onPayment, + ); + return response; + } + return null; + } +} + +class PaymobBillingDataWallet { + String? email; + String? firstName; + String? lastName; + String? phoneNumber; + String? apartment; + String? floor; + String? street; + String? building; + String? postalCode; + String? city; + String? state; + String? country; + String? shippingMethod; + + PaymobBillingDataWallet({ + this.email, + this.firstName, + this.lastName, + this.phoneNumber, + this.apartment, + this.floor, + this.street, + this.building, + this.postalCode, + this.city, + this.state, + this.country, + this.shippingMethod, + }); + + Map toJson() { + return { + "email": box.read(BoxName.email) ?? box.read(BoxName.emailDriver), + "first_name": box.read(BoxName.name) ?? box.read(BoxName.nameDriver), + "last_name": box.read(BoxName.name) ?? box.read(BoxName.lastNameDriver), + "phone_number": box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver), + "apartment": apartment ?? "NA", + "floor": floor ?? "NA", + "building": building ?? "NA", + "street": street ?? "NA", + "postal_code": postalCode ?? "NA", + "city": city ?? "NA", + "state": state ?? "NA", + "country": country ?? "NA", + "shipping_method": shippingMethod ?? "NA", + }; + } +} + +class PaymobIFrameWallet extends StatefulWidget { + const PaymobIFrameWallet({ + Key? key, + required this.redirectURL, + this.onPayment, + }) : super(key: key); + + final String redirectURL; + final void Function(PaymobResponseWallet)? onPayment; + + static Future show({ + required BuildContext context, + required String redirectURL, + void Function(PaymobResponseWallet)? onPayment, + }) => + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return PaymobIFrameWallet( + onPayment: onPayment, + redirectURL: redirectURL, + ); + }, + ), + ); + + @override + State createState() => _PaymobIFrameState(); +} + +class _PaymobIFrameState extends State { + WebViewController? controller; + + @override + void initState() { + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (NavigationRequest request) { + if (request.url.contains('txn_response_code') && + // request.url.contains('successfully') && + request.url.contains('success') && + request.url.contains('id')) { + final params = _getParamFromURL(request.url); + final response = PaymobResponseWallet.fromJson(params); + if (widget.onPayment != null) { + widget.onPayment!(response); + } + Navigator.pop(context, response); + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse(widget.redirectURL)); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: controller == null + ? const Center( + child: CircularProgressIndicator.adaptive(), + ) + : SafeArea( + child: WebViewWidget( + controller: controller!, + ), + ), + ); + } + + Map _getParamFromURL(String url) { + final uri = Uri.parse(url); + final queryParams = uri.queryParameters; + final data = {}; + + queryParams.forEach((key, value) { + if (key.contains('.')) { + final parts = key.split('.'); + data.putIfAbsent(parts.first, () => {}); + (data[parts.first] as Map)[parts.last] = value; + } else { + data[key] = value; + } + }); + + return data; + } +} diff --git a/lib/main.dart b/lib/main.dart index d4077a7..8a8e8ad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:SEFER/controller/payment/paymob/paymob_response.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; @@ -8,7 +9,6 @@ import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:flutter/services.dart'; -import 'package:paymob_payment/paymob_payment.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'constant/api_key.dart'; import 'constant/box_name.dart'; @@ -19,13 +19,16 @@ import 'controller/firebase/local_notification.dart'; import 'controller/functions/location_background_controller.dart'; import 'controller/local/local_controller.dart'; import 'controller/local/translations.dart'; +import 'controller/payment/paymob/paymob_wallet.dart'; import 'firebase_options.dart'; import 'models/db_sql.dart'; import 'splash_screen_page.dart'; final box = GetStorage(); const storage = FlutterSecureStorage(); +// final PaymobPayment paymobPayment = PaymobPayment(); final PaymobPayment paymobPayment = PaymobPayment(); +final PaymobPaymentWallet paymobPaymentWallet = PaymobPaymentWallet(); DbSql sql = DbSql.instance; @pragma('vm:entry-point') @@ -83,6 +86,13 @@ void main() async { DeviceOrientation.portraitDown, ]); } + // PaymobPayment.instance.initialize( + // apiKey: AK + // .payMobApikey, // from dashboard Select Settings -> Account Info -> API Key + // integrationID: int.parse(AK.integrationIdPayMob), + // userTokenExpiration: 200, + // iFrameID: 837992, + // ); PaymobPayment.instance.initialize( apiKey: AK .payMobApikey, // from dashboard Select Settings -> Account Info -> API Key @@ -90,6 +100,13 @@ void main() async { userTokenExpiration: 200, iFrameID: 837992, ); + PaymobPaymentWallet.instance.initialize( + apiKey: AK + .payMobApikey, // from dashboard Select Settings -> Account Info -> API Key + integrationID: 4556056, //int.parse(AK.integrationIdPayMob), + userTokenExpiration: 200, + iFrameID: 837992, + ); runApp(const MyApp()); } diff --git a/lib/views/home/map_widget.dart/left_main_menu_icons.dart b/lib/views/home/map_widget.dart/left_main_menu_icons.dart index 583678a..d11ce1b 100644 --- a/lib/views/home/map_widget.dart/left_main_menu_icons.dart +++ b/lib/views/home/map_widget.dart/left_main_menu_icons.dart @@ -5,7 +5,6 @@ import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/main.dart'; -import 'package:paymob_payment/paymob_payment.dart'; import '../../../constant/colors.dart'; import '../../../controller/functions/tts.dart'; @@ -113,7 +112,12 @@ GetBuilder leftMainMenuIcons() { borderRadius: BorderRadius.circular(15)), child: IconButton( onPressed: () async { - // await PaymobManager().payWithPayMob(100, 'EGP'); + await PaymentController() + .payWithPayMobWallet(context, '100', 'EGP', () {}); + // print(box.read(BoxName.passengerWalletTotal)); + // print(box.read(BoxName.name)); + // print(box.read(BoxName.phone)); + // print(box.read(BoxName.email)); // await Get.find() // .payWithPayMob(context, '1100', 'EGP'); // Initiates a payment with a card using the FlutterPaymob instance diff --git a/lib/views/home/my_wallet/passenger_wallet_dialoge.dart b/lib/views/home/my_wallet/passenger_wallet_dialoge.dart index 5148415..38be839 100644 --- a/lib/views/home/my_wallet/passenger_wallet_dialoge.dart +++ b/lib/views/home/my_wallet/passenger_wallet_dialoge.dart @@ -18,10 +18,10 @@ class PassengerWalletDialoge extends StatelessWidget { Widget build(BuildContext context) { return GetBuilder( builder: (controller) => Positioned( - top: Get.height * .2, + top: Get.height * .1, right: Get.width * .15, left: Get.width * .15, - bottom: Get.height * .2, + bottom: Get.height * .1, child: controller.isPromoSheetDialogue ? Container( decoration: const BoxDecoration( @@ -161,28 +161,72 @@ class PassengerWalletDialoge extends StatelessWidget { }, ), box.read(BoxName.countryCode) == 'Egypt' - ? MyElevatedButton( - title: 'Pay with Credit Card'.tr, - onPressed: () { - if (controller.selectedAmount != 0) { - controller.payWithPayMob( - context, - controller.selectedAmount - .toString(), // Convert int to double - box.read(BoxName.countryCode) == 'Egypt' - ? 'EGP' - : 'JOD', () async { - await controller.addPassengerWallet(); - controller.changePromoSheetDialogue(); - await controller.getPassengerWallet(); - }); - } else { - Toast.show( - context, - 'You will choose one of above !'.tr, - AppColor.redColor); - } - }) + ? Column( + children: [ + MyElevatedButton( + title: '💳 Pay with Credit Card'.tr, + onPressed: () { + if (controller.selectedAmount != 0) { + controller.payWithPayMob( + context, + controller.selectedAmount + .toString(), // Convert int to double + box.read(BoxName.countryCode) == + 'Egypt' + ? 'EGP' + : 'JOD', + () async { + await controller + .addPassengerWallet(); + controller + .changePromoSheetDialogue(); + await controller + .getPassengerWallet(); + }, + ); + } else { + Toast.show( + context, + '⚠️ You need to choose an amount!'.tr, + AppColor.redColor, + ); + } + }, + ), + // Add some spacing between buttons + MyElevatedButton( + kolor: AppColor.yellowColor, + title: '💰 Pay with Wallet'.tr, + onPressed: () { + if (controller.selectedAmount != 0) { + controller.payWithPayMobWallet( + context, + controller.selectedAmount + .toString(), // Convert int to double + box.read(BoxName.countryCode) == + 'Egypt' + ? 'EGP' + : 'JOD', + () async { + await controller + .addPassengerWallet(); + controller + .changePromoSheetDialogue(); + await controller + .getPassengerWallet(); + }, + ); + } else { + Toast.show( + context, + '⚠️ You need to choose an amount!'.tr, + AppColor.redColor, + ); + } + }, + ), + ], + ) : MyElevatedButton( title: 'Pay with Credit Card'.tr, onPressed: () { @@ -191,8 +235,8 @@ class PassengerWalletDialoge extends StatelessWidget { controller.selectedAmount! .toDouble(), // Convert int to double box.read(BoxName.countryCode) != 'Egypt' - ? 'EGP' - : 'USD', () { + ? 'usd' + : 'jod', () { controller.addPassengerWallet(); controller.changePromoSheetDialogue(); controller.getPassengerWallet(); diff --git a/lib/views/home/my_wallet/points_captain.dart b/lib/views/home/my_wallet/points_captain.dart index f78054d..1a2bc84 100644 --- a/lib/views/home/my_wallet/points_captain.dart +++ b/lib/views/home/my_wallet/points_captain.dart @@ -7,6 +7,7 @@ import 'package:SEFER/controller/payment/payment_controller.dart'; import '../../../constant/box_name.dart'; import '../../../main.dart'; +import '../../widgets/elevated_btn.dart'; class PointsCaptain extends StatelessWidget { PaymentController paymentController = Get.put(PaymentController()); @@ -28,17 +29,53 @@ class PointsCaptain extends StatelessWidget { return InkWell( onTap: () async { box.read(BoxName.countryCode) == 'Egypt' - ? await paymentController.payWithPayMob( - context, - pricePoint.toStringAsFixed(2), - box.read(BoxName.countryCode) == 'Egypt' ? 'EGP' : 'JOD', - () async { - await captainWalletController.addDriverPayment( - 'visa', pricePoint); - await captainWalletController.addDriverWallet( - 'visa', countPoint); - await captainWalletController.getCaptainWalletFromBuyPoints(); - }) + ? Get.defaultDialog( + title: 'Which method you will pay'.tr, + titleStyle: AppStyle.title, + content: Column( + children: [ + MyElevatedButton( + title: '💳 Pay with Credit Card'.tr, + onPressed: () async { + Get.back(); + await paymentController.payWithPayMob( + context, + pricePoint.toStringAsFixed(2), + box.read(BoxName.countryCode) == 'Egypt' + ? 'EGP' + : 'JOD', () async { + await captainWalletController.addDriverPayment( + 'visa', pricePoint); + await captainWalletController.addDriverWallet( + 'visa', countPoint); + await captainWalletController + .getCaptainWalletFromBuyPoints(); + }); + }, //51524 + ), + // Add some spacing between buttons + MyElevatedButton( + kolor: AppColor.yellowColor, + title: '💰 Pay with Wallet'.tr, + onPressed: () async { + Get.back(); + await paymentController.payWithPayMobWallet( + context, + pricePoint.toStringAsFixed(2), + box.read(BoxName.countryCode) == 'Egypt' + ? 'EGP' + : 'JOD', () async { + await captainWalletController.addDriverPayment( + 'visa', pricePoint); + await captainWalletController.addDriverWallet( + 'visa', countPoint); + await captainWalletController + .getCaptainWalletFromBuyPoints(); + }); + }, + ), + ], + )) : await paymentController.makePaymentStripe(pricePoint, box.read(BoxName.countryCode) == 'Jordan' ? 'jod' : 'egp', () async { diff --git a/lib/views/home/my_wallet/walet_captain.dart b/lib/views/home/my_wallet/walet_captain.dart index 12070ad..9c18ec0 100644 --- a/lib/views/home/my_wallet/walet_captain.dart +++ b/lib/views/home/my_wallet/walet_captain.dart @@ -318,7 +318,7 @@ class WalletCaptain extends StatelessWidget { box.read(BoxName.countryCode) == 'Jordan' ? '2300' - : '440', + : '450', ), PointsCaptain( kolor: AppColor.yellowColor, diff --git a/pubspec.lock b/pubspec.lock index dc6b9b9..c0944e1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1472,14 +1472,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1" - paymob_payment: - dependency: "direct main" - description: - name: paymob_payment - sha256: "5a7f6ef9c1d538b5bdff52e757f09a97755b0c0cabe9f5098a13cc005477584c" - url: "https://pub.dev" - source: hosted - version: "0.0.1+1" permission_handler: dependency: "direct main" description: @@ -2022,37 +2014,37 @@ packages: source: hosted version: "2.4.0" webview_flutter: - dependency: transitive + dependency: "direct main" description: name: webview_flutter - sha256: "42393b4492e629aa3a88618530a4a00de8bb46e50e7b3993fedbfdc5352f0dbf" + sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932" url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.7.0" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: b54c89fe14a6d26a2a46e24880da0441cdd2bf1f6d01a5b3e1d39558feb1de0b + sha256: f038ee2fae73b509dde1bc9d2c5a50ca92054282de17631a9a3d515883740934 url: "https://pub.dev" source: hosted - version: "3.13.1" + version: "3.16.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: dbe745ee459a16b6fec296f7565a8ef430d0d681001d8ae521898b9361854943 + sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: eebfabfa8a115b535b52031b8b26f7a4b58ceceab378bc9db8762b0fb46f7b5d + sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7 url: "https://pub.dev" source: hosted - version: "3.10.0" + version: "3.13.0" win32: dependency: transitive description: @@ -2094,5 +2086,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.2.3 <4.0.0" + flutter: ">=3.16.6" diff --git a/pubspec.yaml b/pubspec.yaml index cb6da03..52af3a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,7 +60,8 @@ dependencies: flutter_sound: ^9.2.13 record: ^5.0.5 dio: ^5.4.3+1 - paymob_payment: ^0.0.1+1 + # paymob_payment: ^0.0.1+1 + webview_flutter: ^4.7.0