Added Payment Module
parent
75fd726027
commit
78cfdbac49
@ -0,0 +1,68 @@
|
|||||||
|
class PayOrderDetailRespModel {
|
||||||
|
int? id;
|
||||||
|
String? userFullName;
|
||||||
|
String? userMobile;
|
||||||
|
String? userEmail;
|
||||||
|
String? userID;
|
||||||
|
double? amount;
|
||||||
|
String? currency;
|
||||||
|
int? payFortProjectID;
|
||||||
|
int? payFortServiceID;
|
||||||
|
String? description;
|
||||||
|
bool? isPaid;
|
||||||
|
String? language;
|
||||||
|
int? serviceAppointmentID;
|
||||||
|
int? providerSubscriptionID;
|
||||||
|
|
||||||
|
PayOrderDetailRespModel(
|
||||||
|
{this.id,
|
||||||
|
this.userFullName,
|
||||||
|
this.userMobile,
|
||||||
|
this.userEmail,
|
||||||
|
this.userID,
|
||||||
|
this.amount,
|
||||||
|
this.currency,
|
||||||
|
this.payFortProjectID,
|
||||||
|
this.payFortServiceID,
|
||||||
|
this.description,
|
||||||
|
this.isPaid,
|
||||||
|
this.language,
|
||||||
|
this.serviceAppointmentID,
|
||||||
|
this.providerSubscriptionID});
|
||||||
|
|
||||||
|
PayOrderDetailRespModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
userFullName = json['userFullName'];
|
||||||
|
userMobile = json['userMobile'];
|
||||||
|
userEmail = json['userEmail'];
|
||||||
|
userID = json['userID'];
|
||||||
|
amount = json['amount'];
|
||||||
|
currency = json['currency'];
|
||||||
|
payFortProjectID = json['payFort_ProjectID'];
|
||||||
|
payFortServiceID = json['payFort_ServiceID'];
|
||||||
|
description = json['description'];
|
||||||
|
isPaid = json['isPaid'];
|
||||||
|
language = json['language'];
|
||||||
|
serviceAppointmentID = json['serviceAppointmentID'];
|
||||||
|
providerSubscriptionID = json['providerSubscriptionID'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = <String, dynamic>{};
|
||||||
|
data['id'] = id;
|
||||||
|
data['userFullName'] = userFullName;
|
||||||
|
data['userMobile'] = userMobile;
|
||||||
|
data['userEmail'] = userEmail;
|
||||||
|
data['userID'] = userID;
|
||||||
|
data['amount'] = amount;
|
||||||
|
data['currency'] = currency;
|
||||||
|
data['payFort_ProjectID'] = payFortProjectID;
|
||||||
|
data['payFort_ServiceID'] = payFortServiceID;
|
||||||
|
data['description'] = description;
|
||||||
|
data['isPaid'] = isPaid;
|
||||||
|
data['language'] = language;
|
||||||
|
data['serviceAppointmentID'] = serviceAppointmentID;
|
||||||
|
data['providerSubscriptionID'] = providerSubscriptionID;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:mc_common_app/api/api_client.dart';
|
||||||
|
import 'package:mc_common_app/classes/app_state.dart';
|
||||||
|
import 'package:mc_common_app/classes/consts.dart';
|
||||||
|
import 'package:mc_common_app/config/dependencies.dart';
|
||||||
|
import 'package:mc_common_app/models/generic_resp_model.dart';
|
||||||
|
import 'package:mc_common_app/models/payment_models/pay_order_detail_resp_model.dart';
|
||||||
|
|
||||||
|
abstract class PaymentsRepo {
|
||||||
|
Future<PayOrderDetailRespModel> getPayOrderDetails({required int paymentId, required int adId});
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentsRepoImp implements PaymentsRepo {
|
||||||
|
ApiClient apiClient = injector.get<ApiClient>();
|
||||||
|
AppState appState = injector.get<AppState>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PayOrderDetailRespModel> getPayOrderDetails({required int paymentId, required int adId}) async {
|
||||||
|
GenericRespModel adsGenericModel = await apiClient.getJsonForObject(token: appState.getUser.data!.accessToken, (json) => GenericRespModel.fromJson(json), ApiConsts.payForOrderDetailGet);
|
||||||
|
PayOrderDetailRespModel payOrderDetailRespModel = PayOrderDetailRespModel.fromJson(adsGenericModel.data);
|
||||||
|
return payOrderDetailRespModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
|
||||||
|
class MyInAppBrowser extends InAppBrowser {
|
||||||
|
final Function onExitCallback;
|
||||||
|
final Function(String) onLoadStartCallback;
|
||||||
|
|
||||||
|
MyInAppBrowser({
|
||||||
|
required this.onExitCallback,
|
||||||
|
required this.onLoadStartCallback,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future onBrowserCreated() async {
|
||||||
|
log("\n\nBrowser Created!\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future onLoadStart(Uri? url) async {
|
||||||
|
onLoadStartCallback(url.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future onLoadStop(Uri? url) async {
|
||||||
|
log("\n\nStopped $url\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onLoadError(Uri? url, int code, String message) {
|
||||||
|
log("Can't load $url.. Error: $message");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:mc_common_app/classes/consts.dart';
|
||||||
|
import 'package:mc_common_app/services/my_in_app_browser.dart';
|
||||||
|
import 'package:mc_common_app/utils/utils.dart';
|
||||||
|
|
||||||
|
abstract class PaymentService {
|
||||||
|
Future<void> placeAdPayment({
|
||||||
|
required int id,
|
||||||
|
required int paymentType,
|
||||||
|
required Function() onSuccess,
|
||||||
|
required Function() onFailure,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> onBrowserLoadStart({
|
||||||
|
required String url,
|
||||||
|
required Function() onSuccess,
|
||||||
|
required Function() onFailure,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentServiceImp implements PaymentService {
|
||||||
|
MyInAppBrowser? myInAppBrowser;
|
||||||
|
var inAppBrowserOptions = InAppBrowserClassOptions(
|
||||||
|
inAppWebViewGroupOptions:
|
||||||
|
InAppWebViewGroupOptions(crossPlatform: InAppWebViewOptions(useShouldOverrideUrlLoading: true, transparentBackground: false), ios: IOSInAppWebViewOptions(applePayAPIEnabled: true)),
|
||||||
|
crossPlatform: InAppBrowserOptions(hideUrlBar: true, toolbarTopBackgroundColor: Colors.black),
|
||||||
|
android: AndroidInAppBrowserOptions(),
|
||||||
|
ios:
|
||||||
|
IOSInAppBrowserOptions(hideToolbarBottom: true, toolbarBottomBackgroundColor: Colors.white, closeButtonColor: Colors.white, presentationStyle: IOSUIModalPresentationStyle.OVER_FULL_SCREEN));
|
||||||
|
|
||||||
|
String generateBrowserUrl(int id) {
|
||||||
|
String form = Utils.getAdsPaymentBrowserForm(paymentId: 3, adId: id);
|
||||||
|
// form.replaceFirst("SERVICE_URL_VALUE", ApiConsts.paymentWebViewUrl);
|
||||||
|
// form.replaceFirst("PaymentType_Value", 4.toString());
|
||||||
|
// form.replaceFirst("AdID_Value", id.toString());
|
||||||
|
|
||||||
|
var bytes = utf8.encode(form);
|
||||||
|
var base64Str = base64.encode(bytes);
|
||||||
|
return 'data:text/html;base64,$base64Str';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> placeAdPayment({
|
||||||
|
required int id,
|
||||||
|
required int paymentType,
|
||||||
|
required Function() onSuccess,
|
||||||
|
required Function() onFailure,
|
||||||
|
}) async {
|
||||||
|
myInAppBrowser = MyInAppBrowser(onExitCallback: () {
|
||||||
|
log("Browser Exited");
|
||||||
|
}, onLoadStartCallback: (String url) {
|
||||||
|
log("Browser LoadStart");
|
||||||
|
onBrowserLoadStart(onFailure: onFailure, onSuccess: onSuccess, url: url);
|
||||||
|
});
|
||||||
|
await myInAppBrowser!.openUrlRequest(
|
||||||
|
urlRequest: URLRequest(url: Uri.parse("${ApiConsts.paymentWebViewUrl}?PaymentType=$paymentType&AdsID=$id")),
|
||||||
|
options: inAppBrowserOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onBrowserLoadStart({
|
||||||
|
required String url,
|
||||||
|
required Function() onSuccess,
|
||||||
|
required Function() onFailure,
|
||||||
|
}) async {
|
||||||
|
for (var element in ApiConsts.closingUrls) {
|
||||||
|
if (url.contains(element)) {
|
||||||
|
if (myInAppBrowser!.isOpened()) myInAppBrowser!.close();
|
||||||
|
onSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// onFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:mc_common_app/config/routes.dart';
|
||||||
|
import 'package:mc_common_app/models/payment_models/pay_order_detail_resp_model.dart';
|
||||||
|
import 'package:mc_common_app/repositories/payments_repo.dart';
|
||||||
|
import 'package:mc_common_app/services/payments_service.dart';
|
||||||
|
import 'package:mc_common_app/utils/enums.dart';
|
||||||
|
import 'package:mc_common_app/utils/navigator.dart';
|
||||||
|
import 'package:mc_common_app/utils/utils.dart';
|
||||||
|
|
||||||
|
class PaymentVM extends ChangeNotifier {
|
||||||
|
final PaymentService paymentService;
|
||||||
|
final PaymentsRepo paymentRepo;
|
||||||
|
|
||||||
|
PaymentVM({required this.paymentService, required this.paymentRepo});
|
||||||
|
|
||||||
|
PaymentMethodsEnum selectedPaymentMethod = PaymentMethodsEnum.mada;
|
||||||
|
PaymentTypesEnum currentPaymentType = PaymentTypesEnum.ads;
|
||||||
|
|
||||||
|
int currentAdId = -1;
|
||||||
|
|
||||||
|
void updateCurrentAdId({required int id}) {
|
||||||
|
currentAdId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedPaymentMethod(PaymentMethodsEnum selectedMethod) {
|
||||||
|
selectedPaymentMethod = selectedMethod;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onContinuePressed(BuildContext context) async {
|
||||||
|
switch (selectedPaymentMethod) {
|
||||||
|
case PaymentMethodsEnum.mada:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
case PaymentMethodsEnum.visa:
|
||||||
|
await onVisaCardSelected(context);
|
||||||
|
break;
|
||||||
|
case PaymentMethodsEnum.applePay:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
case PaymentMethodsEnum.masterCard:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
case PaymentMethodsEnum.tamara:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onVisaCardSelected(BuildContext context) async {
|
||||||
|
switch (currentPaymentType) {
|
||||||
|
case PaymentTypesEnum.subscription:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
case PaymentTypesEnum.appointment:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
case PaymentTypesEnum.ads:
|
||||||
|
if (currentAdId == -1) return;
|
||||||
|
int paymentType = 3;
|
||||||
|
await paymentService.placeAdPayment(
|
||||||
|
id: currentAdId,
|
||||||
|
paymentType: paymentType,
|
||||||
|
onFailure: () {
|
||||||
|
Utils.showToast("Payment Failed!");
|
||||||
|
},
|
||||||
|
onSuccess: () async {
|
||||||
|
Utils.showLoading(context);
|
||||||
|
PayOrderDetailRespModel payOrderDetailRespModel = await paymentRepo.getPayOrderDetails(paymentId: paymentType, adId: 3);
|
||||||
|
await Future.delayed(Duration(seconds: 2));
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
|
||||||
|
if (payOrderDetailRespModel.isPaid == null || !payOrderDetailRespModel.isPaid!) {
|
||||||
|
Utils.showToast("Payment Failed!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payOrderDetailRespModel.isPaid != null && payOrderDetailRespModel.isPaid!) {
|
||||||
|
Utils.showToast("Payment Successfully Completed!");
|
||||||
|
navigateReplaceWithNameUntilRoute(context, AppRoutes.dashboard);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case PaymentTypesEnum.adReserve:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
case PaymentTypesEnum.request:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,127 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mc_common_app/classes/consts.dart';
|
||||||
|
import 'package:mc_common_app/extensions/int_extensions.dart';
|
||||||
|
import 'package:mc_common_app/extensions/string_extensions.dart';
|
||||||
|
import 'package:mc_common_app/theme/colors.dart';
|
||||||
|
import 'package:mc_common_app/utils/enums.dart';
|
||||||
|
import 'package:mc_common_app/utils/utils.dart';
|
||||||
|
import 'package:mc_common_app/view_models/payment_view_model.dart';
|
||||||
|
import 'package:mc_common_app/widgets/button/show_fill_button.dart';
|
||||||
|
import 'package:mc_common_app/widgets/common_widgets/app_bar.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class PaymentMethodsView extends StatelessWidget {
|
||||||
|
const PaymentMethodsView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: CustomAppBar(
|
||||||
|
title: "Payment Method",
|
||||||
|
profileImageUrl: MyAssets.bnCar,
|
||||||
|
isRemoveBackButton: false,
|
||||||
|
isDrawerEnabled: false,
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10, left: 21, right: 21),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
20.height,
|
||||||
|
"Select Payment Method".toText(fontSize: 18, isBold: true),
|
||||||
|
15.height,
|
||||||
|
Consumer(
|
||||||
|
builder: (BuildContext context, PaymentVM paymentVm, Widget? child) {
|
||||||
|
return Expanded(
|
||||||
|
child: ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: [
|
||||||
|
PaymentMethodContainer(
|
||||||
|
isSelected: paymentVm.selectedPaymentMethod == PaymentMethodsEnum.mada,
|
||||||
|
onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethodsEnum.mada),
|
||||||
|
cardAsset: MyAssets.madaPng),
|
||||||
|
PaymentMethodContainer(
|
||||||
|
isSelected: paymentVm.selectedPaymentMethod == PaymentMethodsEnum.visa,
|
||||||
|
onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethodsEnum.visa),
|
||||||
|
cardAsset: MyAssets.visaPng),
|
||||||
|
PaymentMethodContainer(
|
||||||
|
isSelected: paymentVm.selectedPaymentMethod == PaymentMethodsEnum.applePay,
|
||||||
|
onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethodsEnum.applePay),
|
||||||
|
cardAsset: MyAssets.applePayPng),
|
||||||
|
PaymentMethodContainer(
|
||||||
|
isSelected: paymentVm.selectedPaymentMethod == PaymentMethodsEnum.masterCard,
|
||||||
|
onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethodsEnum.masterCard),
|
||||||
|
cardAsset: MyAssets.mastercardPng),
|
||||||
|
PaymentMethodContainer(
|
||||||
|
isSelected: paymentVm.selectedPaymentMethod == PaymentMethodsEnum.tamara,
|
||||||
|
onTap: () => paymentVm.updateSelectedPaymentMethod(PaymentMethodsEnum.tamara),
|
||||||
|
cardAsset: MyAssets.tamaraEngPng),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ShowFillButton(
|
||||||
|
maxHeight: 55,
|
||||||
|
title: "Continue",
|
||||||
|
onPressed: () {
|
||||||
|
context.read<PaymentVM>().onContinuePressed(context);
|
||||||
|
},
|
||||||
|
backgroundColor: MyColors.darkPrimaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentMethodContainer extends StatelessWidget {
|
||||||
|
final bool isSelected;
|
||||||
|
final String cardAsset;
|
||||||
|
final Function() onTap;
|
||||||
|
|
||||||
|
const PaymentMethodContainer({Key? key, required this.isSelected, required this.cardAsset, required this.onTap}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 10),
|
||||||
|
width: double.infinity,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: MyColors.white,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(color: isSelected ? Colors.green : Colors.transparent, width: isSelected ? 2.0 : 0.0),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
decoration: Utils.containerColorRadiusBorderWidth(isSelected ? MyColors.primaryColor : Colors.transparent, 100, Colors.grey, 0.5),
|
||||||
|
),
|
||||||
|
12.width,
|
||||||
|
Container(
|
||||||
|
height: 80.0,
|
||||||
|
width: 80.0,
|
||||||
|
padding: EdgeInsets.all(7.0),
|
||||||
|
child: Image.asset(cardAsset),
|
||||||
|
),
|
||||||
|
Utils.mFlex(1),
|
||||||
|
isSelected ? Utils.statusContainerChip(text: "Selected", padding: EdgeInsets.symmetric(vertical: 6, horizontal: 10)) : SizedBox(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue