import 'package:Tripz/constant/box_name.dart'; import 'package:Tripz/main.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; import '../../functions/encrypt_decrypt.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, }, ); 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, }, ); 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); } 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; } //51624 } 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": encryptionHelper .decryptData(box.read(BoxName.name).toString().split(' ')[0]) .toString(), "last_name": encryptionHelper .decryptData(box.read(BoxName.name).toString().split(' ')[1]) .toString(), "phone_number": encryptionHelper.decryptData(box.read(BoxName.phone)), "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; } }