package com.app.features.checkout import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.app.base.viewmodel.AppBaseViewModel import com.app.core.enums.DeliveryTime import com.app.core.enums.ShippingType import com.app.core.errors.AppError import com.app.core.graphql.errorhandling.NetworkErrorHandlingImpl import com.app.core.models.* import com.app.core.repositories.AuthenticationRepo import com.app.core.repositories.CheckoutRepository import com.app.core.repositories.StoreConfigRepo import com.app.core.repositories.UserRepository import com.app.core.usecases.* import com.app.core.utils.LocaleManager import com.app.features.checkout.models.AppTempAddress import com.app.features.checkout.models.AppTempContactInfo import com.app.features.checkout.models.UIDeliveryAddress import com.app.features.checkout.usecases.* import com.payfort.sdk.android.dependancies.base.FortInterfaces import com.payfort.sdk.android.dependancies.models.FortRequest import com.paymob.acceptsdk.IntentConstants.* import com.robusta.bootstrap.data.api.observable.addTo import com.robusta.bootstrap.service.schedulers.SchedulersService import com.robusta.bootstrap.service.session.SessionService import com.robustastudio.checkout_feat.CartPriceDetails import com.robustastudio.ecommerce.analytics_feat.AnalyticsConstants import com.robustastudio.ecommerce.analytics_feat.AnalyticsService import com.robustastudio.magentocore.model.IAvailablePaymentMethod import com.robustastudio.magentocore.model.product.ProductSectionType import com.robustastudio.magentocore.utils.ProductInfoCache import com.robustastudio.networkclient.utils.Errors import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import org.koin.core.component.inject import java.util.HashMap const val FREE_PAYMENT_KEY = "free" class CheckoutViewModel( private val checkoutRepository: CheckoutRepository, private val sessionService: SessionService, schedulersService: SchedulersService, private val getUserAddressesUseCase: GetUserAddressesUseCase, private val getDefaultUserLocationUseCase: GetDefaultUserLocationUseCase, private val getAvailableDeliveryMethodsUseCase: GetAvailableDeliveryMethodsUseCase, private val setShippingMethodUseCase: SetShippingMethodUseCase, private val setDeliveryTimeUseCase: SetDeliveryTimeUseCase, private val getAvailablePaymentMethodsUseCase: GetAvailablePaymentMethodsUseCase, private val authenticationRepo: AuthenticationRepo, private val setPaymentMethodOnCartUseCase: SetPaymentMethodOnCartUseCase, private val getAvailableRegionsUseCase: GetAvailableRegionsUseCase, private val fetchGovernoratesUseCase: FetchGovernoratesUseCase, private val proceedWithCreditCardUseCase: ProceedWithCreditCardUseCase, private val proceedWithCashUseCase: ProceedWithCashUseCase, private val fetchCartPriceDetailsUseCase: FetchCartPriceDetailsUseCase, private val fetchDeliveryDatesUseCase: FetchDeliveryDatesUseCase, private val fetchCartItemTypesUseCase: FetchCartItemTypesUseCase, private val setOrderSourceOnCartUseCase: SetOrderSourceOnCartUseCase, private val getPayFortSdkTokenUseCase: GetPayFortSdkTokenUseCase, private val applyLoyaltyPointsUseCase: ApplyLoyaltyPointsUseCase, private val getPaymobIframeUrlOrReferenceNumberUseCase: GetPaymobIframeUrlOrReferenceNumberUseCase, private val userRepository: UserRepository, private val getLoyaltyPointsExchangeRatesUseCase: GetLoyaltyPointsExchangeRatesUseCase, private val storeConfigRepo: StoreConfigRepo, ) : AppBaseViewModel(schedulersService), FortInterfaces.OnTnxProcessed { override val stateMachine = getCheckoutStateMachine() private val analyticsService: AnalyticsService by inject() private var selectedDeliveryAddress: UIDeliveryAddress? = null private var cachedUserAddresses: List = mutableListOf() private var selectedPaymentMethod: AppAvailablePaymentMethod? = null private var availablePaymentMethods: List? = null private var selectedShippingMethod: AppAvailableDeliveryMethod? = null private var availableShippingMethods: List? = null private var selectedDeliveryDay: AppDeliveryDay? = null var availableDeliveryDays: List? = null private var selectedDeliveryTime: DeliveryTime = DeliveryTime.ASAP private var selectedDeliveryInterval: AppDeliveryInterval? = null private var availableDeliveryIntervals: List? = null private var selectedStore: AppShippingLocation? = null private var availableCities: List? = null private var guestContactInfo: AppTempContactInfo? = null private var guestTempAddress: AppTempAddress? = null private var cartPriceDetails: CartPriceDetails? = null private var selectedShippingType: ShippingType = ShippingType.HOME private var hasNonGroceryItems: Boolean = false private var hasGroceryItems: Boolean = false val defaultLocation = getDefaultUserLocationUseCase.execute() private val _priceDetails = MutableLiveData() val priceDetails: LiveData get() = _priceDetails private val _promoCodeState = MutableLiveData(PromoCodeState.Input) val promoCodeStateLiveData: LiveData get() = _promoCodeState init { analyticsService.logBeginCheckout() analyticsService.logEventToFB(AnalyticsConstants.FACEBOOK_ANALYTICS_EVENTS.EVENT_INITIATE_CHECKOUT) loadShippingDataForDelivery() } fun loadShippingDataForDelivery(shippingAddress: UIDeliveryAddress? = null) { selectedShippingType = ShippingType.HOME if (isGuest()) { fetchGovernoratesUseCase.execute() .zipWith(fetchCartPriceDetailsUseCase.execute()) { regions, prices -> Pair(regions, prices) }.compose(getIOTransformer()).doOnSubscribe { //SHOW LOADING handleEvent(CheckoutEvent.StartLoadingCheckoutData) }.subscribe({ cartPriceDetails = it.second availableCities = it.first // handleEvent( // CheckoutEvent.OnShippingDataLoaded( // availableDeliveryDays = emptyList(), // availableAddresses = emptyList(), // isGuest = true, // selectedShippingType = ShippingType.HOME, // cities = it.first.sortedBy { it.cities.isEmpty() }, // cartPriceDetails = it.second, // electronicsCount = electronicsCount, // groceriesCount = groceriesCount // ) // ) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnFailedToLoadCheckoutData(it)) }).addTo(compositeDisposable) } else { val location = getDefaultUserLocationUseCase.execute() getUserAddressesUseCase.execute().zipWith(getAvailableRegionsUseCase.execute(), ::Pair) .flatMap { cachedUserAddresses = it.first.mapIndexed { index, address -> val isInDeliveryZone = isAddressSupportedByStoreCode( address = address, storeCode = location?.availableStore?.storeCode!!, it.second ) UIDeliveryAddress( isInDeliveryZone = isInDeliveryZone, id = address.id, addressName = if (address.addressName != null && address.addressName!!.isNotEmpty()) address.addressName else "Address #${index + 1}", district = address.district, city = address.city, region = address.region, apartment = address.apartment, lastName = address.lastName, firstName = address.firstName, defaultShipping = address.defaultShipping, floor = address.floor, defaultBilling = address.defaultBilling, building = address.building, countryCode = address.countryCode, customerNotes = address.customerNotes, street = address.street, phone = address.phone, lat = address.lat, lng = address.lng, compound = address.compound, ) } if (cachedUserAddresses.isNotEmpty()) { //WHAT IF THE SHIPPING ADDRESS IS NOT IN THE LOADED LIST? selectedDeliveryAddress = shippingAddress ?: cachedUserAddresses.first { it.isInDeliveryZone } getAvailableDeliveryMethodsUseCase.execute( selectedDeliveryAddress!!.id!! ).zipWith( fetchDeliveryDatesUseCase.execute( selectedDeliveryAddress!!.city!!.code, selectedDeliveryAddress!!.district!!.id.toString() ) ) { methods: List, days: List -> Pair(methods, days) } } else Single.just( Pair( emptyList(), emptyList() ) ) }.flatMapCompletable { availableShippingMethods = it.first availableDeliveryDays = it.second if (it.first.isNotEmpty() && it.second.isNotEmpty()) { selectedShippingMethod = it.first.filterNot { it.methodCode.contains("store") }.first() selectedDeliveryDay = it.second.first() availableDeliveryIntervals = selectedDeliveryDay!!.intervals selectedDeliveryInterval = availableDeliveryIntervals!!.first() setShippingMethodUseCase.execute( selectedShippingMethod!! ).andThen( setDeliveryTimeUseCase.execute( selectedDeliveryDay!!.date, selectedDeliveryInterval!!.id ) ) } else Completable.complete() }.andThen(Single.zip( fetchCartPriceDetailsUseCase.execute(), fetchCartItemTypesUseCase.execute(), getLoyaltyPointsExchangeRatesUseCase.execute() ) { price, items, pointsRate -> Triple(price, items, pointsRate) }).flatMap { if (it.first.grandTotal == 0.0) { selectedPaymentMethod = AppAvailablePaymentMethod( FREE_PAYMENT_KEY, "", 0f ) setPaymentMethodOnCartUseCase.execute( AppAvailablePaymentMethod( FREE_PAYMENT_KEY, "", 0f ) ).andThen(Single.just(it)) } else { getAvailablePaymentMethodsUseCase.execute().flatMapCompletable { list -> availablePaymentMethods = list selectedPaymentMethod = list.first() setPaymentMethodOnCartUseCase.execute(list.first()) }.andThen(Single.just(it)) } }.compose(getIOTransformer()).doOnSubscribe { //SHOW LOADING handleEvent(CheckoutEvent.StartLoadingCheckoutData) }.subscribe({ cartPriceDetails = it.first hasNonGroceryItems = it.second.contains(ProductSectionType.ELECTRONICS) || it.second.contains( ProductSectionType.CLOTHING ) hasNonGroceryItems = it.second.contains(ProductSectionType.NON_ELECTRONICS) handleEvent( CheckoutEvent.OnShippingDataLoaded( availableDeliveryDays = availableDeliveryDays, availableAddresses = cachedUserAddresses, isGuest = isGuest(), hasNonGroceryItems = hasNonGroceryItems, hasGroceryItems = hasGroceryItems, selectedShippingType = ShippingType.HOME, selectedDeliveryAddress = selectedDeliveryAddress!!, cartPriceDetails = cartPriceDetails!!, availablePaymentMethods = if (selectedPaymentMethod!!.code == FREE_PAYMENT_KEY) emptyList() else availablePaymentMethods!!, selectedPaymentMethod = selectedPaymentMethod!!, selectedDeliveryDay = selectedDeliveryDay!!, selectedDeliveryInterval = selectedDeliveryInterval!!, user = userRepository.getUserFromCache()!!, pointsRate = it.third ) ) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnFailedToLoadCheckoutData(it)) }).addTo(compositeDisposable) } } private fun canProceedToPayment(): Boolean = if (isGuest()) { (selectedDeliveryAddress != null || selectedStore != null || guestTempAddress != null) && if (selectedShippingType == ShippingType.STORE) guestContactInfo != null else selectedShippingMethod != null } else selectedDeliveryAddress != null private fun loadShippingDataForPickup() { // selectedShippingType = ShippingType.STORE // getAvailableStoresUseCase.execute() // .compose(getIOTransformer()) // .doOnSubscribe { // //SHOW LOADING // handleEvent(CheckoutEvent.StartLoadingCheckoutData) // } // .subscribe({ // availableStores = it // selectedStore = it.first() // handleEvent( // CheckoutEvent.OnShippingDataLoaded( // availableDeliveryDays = availableDeliveryDays, // availableAddresses = cachedUserAddresses, // isGuest = isGuest(), // selectedShippingType = ShippingType.STORE, // selectedDeliveryAddress = selectedDeliveryAddress, // selectedMethod = selectedShippingMethod, // availableStores = availableStores, // selectedStore = selectedStore, // canProceedToPayment = canProceedToPayment(), // cartPriceDetails = cartPriceDetails!!, // electronicsCount = electronicsCount, // groceriesCount = groceriesCount // ) // ) // }, NetworkErrorHandlingImpl { // handleEvent(CheckoutEvent.OnFailedToLoadCheckoutData(it)) // }).addTo(compositeDisposable) } private fun isGuest() = !authenticationRepo.isUserLoggedIn() private fun fetchPaymentMethods(): Single> { return checkoutRepository.getAvailablePaymentMethods().map { it.map(IAvailablePaymentMethod::toModel) } } fun onPaymentMethodSelected(paymentMethod: AppAvailablePaymentMethod) { setPaymentMethodOnCartUseCase.execute( paymentMethod ).andThen(fetchCartPriceDetailsUseCase.execute()).compose(getIOTransformer()) .doOnSubscribe { selectedPaymentMethod = paymentMethod handleEvent(CheckoutEvent.OnPaymentMethodSelected(paymentMethod)) }.subscribe({ handleEvent(CheckoutEvent.OnCartPricesUpdated(it)) cartPriceDetails = it }, NetworkErrorHandlingImpl { simpleFeedbackLiveEvent.value = it }).addTo(compositeDisposable) selectedPaymentMethod = paymentMethod } // // //SETS ADDRESS ON CART AND GET AVAILABLE DELIVERY METHODS // //THEN SETS THE FIRST DELIVERY METHOD AS SELECTED // private fun setDeliveryAddressAndGetAvailableMethods(): Single { // // There are 2 cases when adding a shipping address: // // 1. If there is a default address, send the default address id. // // 2. If there is no default address, send the first address id. // val address = cachedUserAddresses.find { address -> // address.defaultShipping == true // } ?: cachedUserAddresses.first() // selectShippingAddress(address) // return checkoutRepository.setBillingAddressAndGetDeliveryMethods(address.id) // .map { // it.map(IAvailableDeliveryMethod::toModel) // }.doOnSuccess { // availableShippingMethods = it // } // .flatMap { // checkoutRepository.setShippingMethodOnCart( // carrierCode = selectedShippingMethod?.carrierCode ?: it.first().carrierCode, // methodCode = selectedShippingMethod?.methodCode ?: it.first().methodCode, // ).andThen(Single.just(it.first())) // } // } fun onASAPDeliverySelected() { selectedDeliveryTime = DeliveryTime.ASAP selectedDeliveryDay = availableDeliveryDays!!.first() selectedDeliveryInterval = selectedDeliveryDay!!.intervals.first() setDeliveryTimeUseCase.execute( selectedDeliveryDay!!.date, selectedDeliveryInterval!!.id ).compose(getIOTransformer()).doOnSubscribe { handleEvent( CheckoutEvent.OnDeliveryTimeSelected( DeliveryTime.ASAP, selectedDeliveryInterval = selectedDeliveryInterval!!, selectedDeliveryDay = selectedDeliveryDay!!, blockPlaceOrder = true ) ) }.subscribe({ handleEvent( CheckoutEvent.OnDeliveryTimeSelected( DeliveryTime.ASAP, selectedDeliveryInterval = selectedDeliveryInterval!!, selectedDeliveryDay = selectedDeliveryDay!!, blockPlaceOrder = false ) ) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnFailedToLoadCheckoutData(it)) }).addTo(compositeDisposable) } fun clearCart() { ProductInfoCache.resetCartCount() sessionService.removeCartId() } fun handleTransaction(resultCode: Int, paymentId: String?) { when (resultCode) { TRANSACTION_SUCCESSFUL, TRANSACTION_SUCCESSFUL_PARSING_ISSUE -> { handleEvent( CheckoutEvent.OnOrderPlacedSuccessfully( listOf(paymentId!!) ) ) } TRANSACTION_REJECTED, TRANSACTION_REJECTED_PARSING_ISSUE -> { handleEvent(CheckoutEvent.OnPlaceOrderFailed(AppError.UNKNOWN_ERROR("تم رفض المعاملة"))) } USER_CANCELED, USER_CANCELED_3D_SECURE_VERIFICATION, USER_CANCELED_3D_SECURE_VERIFICATION_PARSING_ISSUE -> { handleEvent(CheckoutEvent.OnPlaceOrderFailed(AppError.UNKNOWN_ERROR("تم إلغاء المعاملة"))) } else -> { handleEvent(CheckoutEvent.OnPlaceOrderFailed(AppError.UNKNOWN_ERROR("حدث خطأ محهول"))) } } } private fun proceedWithCreditCard() { analyticsService.logPaymentMethodOptionSelection(selectedPaymentMethod!!.title) proceedWithCreditCardUseCase.execute(selectedPaymentMethod!!.code) .compose(getIOTransformer()).doOnSubscribe { handleEvent(CheckoutEvent.OnPlaceOrderClicked) }.subscribe({ handleEvent(CheckoutEvent.PlaceOrderWithCC(it)) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnPlaceOrderFailed(it)) }).addTo(compositeDisposable) } private fun proceedWithPayFort() { analyticsService.logPaymentMethodOptionSelection(selectedPaymentMethod!!.title) (checkoutRepository.setOrderSource()) .andThen( getPayFortSdkTokenUseCase.execute() ) .compose(getIOTransformer>()) .doOnSubscribe { handleEvent(CheckoutEvent.OnPlaceOrderClicked) } .subscribe({ // prepare payment request val fortRequest = FortRequest() fortRequest.isShowResponsePage = true fortRequest.requestMap = collectRequestMap( it.first, it.second, sessionService.getCurrentUser(AppUser::class.java)!!.emailAddress, cartPriceDetails!!.grandTotal.toFloat() ) // to [display/use] the SDK response page simpleFeedbackLiveEvent.value = CheckoutClickActions.ProceedWithPayFortPayment( fortRequest ) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnPlaceOrderFailed(it)) }) .addTo(compositeDisposable) } private fun proceedWithPaymob() { analyticsService.logPaymentMethodOptionSelection(selectedPaymentMethod!!.title) setOrderSourceOnCartUseCase.execute() .andThen(getPaymobIframeUrlOrReferenceNumberUseCase.execute()) .compose(getIOTransformer()).doOnSubscribe { handleEvent(CheckoutEvent.OnPlaceOrderClicked) }.subscribe({ handleEvent(CheckoutEvent.OnPendingGateway) simpleFeedbackLiveEvent.value = CheckoutClickActions.ProceedWithPaymobPayment( iFrameUrl = if (selectedPaymentMethod!!.code != PaymentMethod.PAYMOB_KIOSK.code) it else null, referenceNumber = if (selectedPaymentMethod!!.code == PaymentMethod.PAYMOB_KIOSK.code) it else null ) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnPlaceOrderFailed(it)) }).addTo(compositeDisposable) } fun proceedWithCash() { analyticsService.logPaymentMethodOptionSelection(selectedPaymentMethod!!.title) setOrderSourceOnCartUseCase.execute().andThen( proceedWithCashUseCase.execute() ).doOnSuccess { clearCart() }.compose(getIOTransformer>()).doOnSubscribe { handleEvent(CheckoutEvent.OnPlaceOrderClicked) }.subscribe({ handleEvent(CheckoutEvent.OnOrderPlacedSuccessfully(it)) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnPlaceOrderFailed(it)) }).addTo(compositeDisposable) } fun placeOrder() { val minimumOrderAmount = getMinimumOrderAmount() if (cartPriceDetails!!.subtotalWithDiscountExcludingTax < minimumOrderAmount) { showSideEffect(CheckoutSideEffect.ShowMinimumOrderAmountError(minimumOrderAmount)) } else { // if (selectedDeliveryAddress?.phoneVerified == true) { if (selectedPaymentMethod?.code?.contains("accept") == true) { proceedWithPaymob() } else if (selectedPaymentMethod?.code?.contains("fort") == true) { proceedWithPayFort() } else { proceedWithCash() } // } else { // simpleFeedbackLiveEvent.value = // CheckoutClickActions.VerifyAddress(selectedDeliveryAddress!!) // } } } fun onAddressSelected(shippingAddress: UIDeliveryAddress) { fetchDeliveryDatesUseCase.execute( selectedDeliveryAddress!!.city!!.code, selectedDeliveryAddress!!.district!!.id.toString() ).flatMap { availableDeliveryDays = it if (it.isNotEmpty()) { selectedDeliveryDay = it.first() availableDeliveryIntervals = selectedDeliveryDay!!.intervals selectedDeliveryInterval = availableDeliveryIntervals!!.first() setDeliveryTimeUseCase.execute( selectedDeliveryDay!!.date, selectedDeliveryInterval!!.id ).andThen(Single.just(it)) } else Single.just(it) }.compose(getIOTransformer()).doOnSubscribe { selectedDeliveryAddress = shippingAddress handleEvent(CheckoutEvent.OnAddressSelected(shippingAddress)) }.subscribe({ handleEvent( CheckoutEvent.OnDeliveryDaysUpdated( it, selectedDeliveryDay = selectedDeliveryDay!!, selectedDeliveryInterval = selectedDeliveryInterval!! ) ) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnPlaceOrderFailed(it)) }).addTo(compositeDisposable) } fun onAddressVerified() { val location = getDefaultUserLocationUseCase.execute() getUserAddressesUseCase.execute().zipWith(getAvailableRegionsUseCase.execute(), ::Pair) .doOnSuccess { cachedUserAddresses = it.first.mapIndexed { index, address -> val isInDeliveryZone = isAddressSupportedByStoreCode( address = address, storeCode = location?.availableStore?.storeCode!!, it.second ) UIDeliveryAddress( isInDeliveryZone = isInDeliveryZone, id = address.id, addressName = if (address.addressName != null && address.addressName!!.isNotEmpty()) address.addressName else "Address #${index + 1}", district = address.district, city = address.city, region = address.region, apartment = address.apartment, lastName = address.lastName, firstName = address.firstName, defaultShipping = address.defaultShipping, floor = address.floor, defaultBilling = address.defaultBilling, building = address.building, countryCode = address.countryCode, customerNotes = address.customerNotes, street = address.street, phone = address.phone, lat = address.lat, lng = address.lng, compound = address.compound, ) } selectedDeliveryAddress = cachedUserAddresses.find { it.id == selectedDeliveryAddress?.id } }.compose(getIOTransformer()).doOnSubscribe { handleEvent(CheckoutEvent.StartLoadingCheckoutData) }.subscribe({}, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnFailedToLoadCheckoutData(it)) }).addTo(compositeDisposable) } fun onAddressCreated(shippingAddress: AppShippingAddress) { val location = getDefaultUserLocationUseCase.execute() getAvailableRegionsUseCase.execute().compose(getIOTransformer()).subscribe({ loadShippingDataForDelivery(shippingAddress.let { address -> val isInDeliveryZone = isAddressSupportedByStoreCode( address = address, storeCode = location?.availableStore?.storeCode!!, it ) UIDeliveryAddress( isInDeliveryZone = isInDeliveryZone, id = address.id, addressName = if (address.addressName != null && address.addressName!!.isNotEmpty()) address.addressName else "", district = address.district, city = address.city, region = address.region, apartment = address.apartment, lastName = address.lastName, firstName = address.firstName, defaultShipping = address.defaultShipping, floor = address.floor, defaultBilling = address.defaultBilling, building = address.building, countryCode = address.countryCode, customerNotes = address.customerNotes, street = address.street, phone = address.phone, lat = address.lat, lng = address.lng, compound = address.compound, ) }) }, { }).addTo(compositeDisposable) } fun applyCouponToCart(couponCode: String) { checkoutRepository.applyCouponToCart(couponCode).compose(getIOTransformer()).doOnSubscribe { _promoCodeState.value = PromoCodeState.Applying handleEvent(CheckoutEvent.OnCouponStateChange) }.subscribe({ _promoCodeState.value = PromoCodeState.Showing(it.appliedCouponCode!!) handleEvent(CheckoutEvent.OnCartPricesUpdated(it)) }, NetworkErrorHandlingImpl { if (it is AppError.MagentoError && it.magentoThrowable.getWhichErrors() == Errors.WRONG_COUPON) _promoCodeState.value = PromoCodeState.InvalidPromoCode else if (it is AppError.MagentoError && it.magentoThrowable.getWhichErrors() == Errors.COUPON_ERROR) _promoCodeState.value = PromoCodeState.AlreadyAppliedAnotherPromoCode else { _promoCodeState.value = PromoCodeState.Input simpleFeedbackLiveEvent.value = it } }).addTo(compositeDisposable) } fun removeCouponFromCart() { checkoutRepository.removeCouponFromCart().compose(getIOTransformer()).doOnSubscribe { handleEvent(CheckoutEvent.OnCouponStateChange) }.subscribe({ handleEvent(CheckoutEvent.OnCartPricesUpdated(it)) _priceDetails.value = it _promoCodeState.value = PromoCodeState.Input }, NetworkErrorHandlingImpl { simpleFeedbackLiveEvent.value = it if (priceDetails.value?.appliedCouponCode != null) _promoCodeState.value = PromoCodeState.Showing(priceDetails.value?.appliedCouponCode!!) else _promoCodeState.value = PromoCodeState.Input }).addTo(compositeDisposable) } fun onFailedToInitializeOpaySdk(exception: Exception) { loggingService.e("Checkout Fragment", "opay Exception during payment") loggingService.stackTrack(exception) handleEvent(CheckoutEvent.OnPlaceOrderFailed(AppError.UNKNOWN_ERROR(exception.message))) } fun onSuccess( orderNumbers: List ) { //VERY IMPORTANT TO CLEAR CART FROM ANY CACHED COUNTS clearCart() handleEvent( CheckoutEvent.OnOrderPlacedSuccessfully( orderNumbers ) ) } fun onFailure(message: String) { handleEvent(CheckoutEvent.OnPlaceOrderFailed(AppError.UNKNOWN_ERROR(message))) } fun proceedToPayment(note: String?) { // if (selectedShippingType == ShippingType.STORE) // setPickupFromStoreUseCase.execute(selectedStore!!, guestContactInfo) // .zipWith(getAvailablePaymentMethodsUseCase.execute()) { shippingMethod, paymentMethods -> // Pair(shippingMethod, paymentMethods) // } // .flatMap { result -> // selectedPaymentMethod = result.second.first() // setPaymentMethodOnCartUseCase.execute(result.second.first()) // .andThen(fetchCartPriceDetailsUseCase.execute()).flatMap { // cartPriceDetails = it // addNoteToCartUseCase.execute(note).andThen( // Single.just(result) // ) // } // } // .compose(getIOTransformer()) // .doOnSubscribe { // handleEvent(CheckoutEvent.OnProceedToPayment) // } // .subscribe({ // selectedShippingMethod = it.first // availablePaymentMethods = it.second // handleEvent( // CheckoutEvent.OnPaymentMethodsLoaded( // it.second, // selectedPaymentMethod!!, // selectedShippingMethod!!, // cartPriceDetails!! // ) // ) // }, NetworkErrorHandlingImpl { // handleEvent(CheckoutEvent.OnFailedToLoadCheckoutData(it)) // }).addTo(compositeDisposable) // else { // getAvailablePaymentMethodsUseCase.execute() // .flatMap { list -> // selectedPaymentMethod = list.first() // setPaymentMethodOnCartUseCase.execute(list.first()) // .andThen(fetchCartPriceDetailsUseCase.execute()).flatMap { // cartPriceDetails = it // addNoteToCartUseCase.execute(note).andThen( // Single.just(list) // ) // } // } // .compose(getIOTransformer()) // .doOnSubscribe { // handleEvent(CheckoutEvent.OnProceedToPayment) // } // .subscribe({ // availablePaymentMethods = it // handleEvent( // CheckoutEvent.OnPaymentMethodsLoaded( // it, // selectedPaymentMethod!!, // selectedShippingMethod!!, // cartPriceDetails!! // ) // ) // }, NetworkErrorHandlingImpl { // handleEvent(CheckoutEvent.OnFailedToLoadCheckoutData(it)) // }).addTo(compositeDisposable) // } } fun onShippingMethodSelected(shippingType: ShippingType) { if (shippingType == ShippingType.HOME) { loadShippingDataForDelivery() } else { loadShippingDataForPickup() } } fun onStoreSelected(store: AppShippingLocation) { selectedStore = store } fun editShippingDetails() { handleEvent(CheckoutEvent.GoBackToShipping) } fun setSelectedDeliverySlot(time: AppDeliveryInterval?, day: AppDeliveryDay?) { if (time != null && day != null) { selectedDeliveryInterval = time selectedDeliveryDay = day selectedDeliveryTime = DeliveryTime.SCHEDULE setDeliveryTimeUseCase.execute( selectedDeliveryDay!!.date, selectedDeliveryInterval!!.id ).compose(getIOTransformer()).doOnSubscribe { handleEvent( CheckoutEvent.OnDeliveryTimeSelected( DeliveryTime.SCHEDULE, selectedDeliveryInterval = selectedDeliveryInterval!!, selectedDeliveryDay = selectedDeliveryDay!!, blockPlaceOrder = true ) ) }.subscribe({ handleEvent( CheckoutEvent.OnDeliveryTimeSelected( DeliveryTime.SCHEDULE, selectedDeliveryInterval = selectedDeliveryInterval!!, selectedDeliveryDay = selectedDeliveryDay!!, blockPlaceOrder = false ) ) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnFailedToLoadCheckoutData(it)) }).addTo(compositeDisposable) } } fun toggleDeliverySection(expanded: Boolean) { handleEvent(CheckoutEvent.OnToggleDeliveryExpanded(expanded)) } fun togglePaymentSection(expanded: Boolean) { handleEvent(CheckoutEvent.OnTogglePaymentExpanded(expanded)) } private fun isAddressSupportedByStoreCode( address: AppShippingAddress, storeCode: String, regions: List ) = regions.find { it.id == address.region?.id }?.cities?.find { it.id == address.city?.id }?.districts?.find { it.id == address.district?.id }?.store?.storeCode == storeCode fun refreshScreen() { if (_stateLiveData.value == CheckoutState.LoadingCheckoutData) return else loadShippingDataForDelivery() } fun applyLoyaltyPoints(points: Int) { handleEvent(CheckoutEvent.OnApplyLoyaltyPoints) applyLoyaltyPointsUseCase.execute(points) .flatMap({ userRepository.getUserFromNetwork() }, { cartPrices, user -> Pair(cartPrices, user) }).compose(getIOTransformer()).subscribe({ handleEvent( CheckoutEvent.OnApplyLoyaltyPointsSuccessfully(it.first, it.second) ) }, NetworkErrorHandlingImpl { handleEvent(CheckoutEvent.OnApplyLoyaltyPointsFailed(it)) }).addTo(compositeDisposable) } private fun getMinimumOrderAmount(): Int = storeConfigRepo.getMinimumOrderAmount() override fun onCancel( requestParamsMap: MutableMap?, responseMap: MutableMap? ) { loggingService.d("Cancelled ", responseMap.toString()); loggingService.d("FortSDK failure", responseMap.toString()) handleEvent(CheckoutEvent.OnPlaceOrderFailed(AppError.UNKNOWN_ERROR(null))) } override fun onSuccess( requestParamsMap: MutableMap?, fortResponseMap: MutableMap? ) { loggingService.i("Success ", fortResponseMap.toString()) //VERY IMPORTANT TO CLEAR CART FROM ANY CACHED COUNTS clearCart() handleEvent( CheckoutEvent.OnOrderPlacedSuccessfully( (fortResponseMap?.get("merchant_reference") as String).split("_") ) ) } override fun onFailure( requestParamsMap: MutableMap?, fortResponseMap: MutableMap? ) { loggingService.e("Failure ", fortResponseMap.toString()) loggingService.d("FortSDK failure", fortResponseMap.toString()) handleEvent(CheckoutEvent.OnPlaceOrderFailed(AppError.UNKNOWN_ERROR(null))) } fun onFailedToInitializePayFortSdk(exception: Exception) { loggingService.e("Checkout Fragment", "PayFort Exception during payment") loggingService.stackTrack(exception) handleEvent(CheckoutEvent.OnPlaceOrderFailed(AppError.UNKNOWN_ERROR(exception.message))) } //PAYFORT SDK REQUEST private fun collectRequestMap( sdkToken: String, merchantRef: String, email: String, total: Float ): Map { val requestMap: MutableMap = HashMap() requestMap["command"] = "AUTHORIZATION" requestMap["customer_email"] = email requestMap["currency"] = "EGP" requestMap["amount"] = (total * 100).toInt().toString() requestMap["language"] = LocaleManager.getLocale() // requestMap["return_url"] = payfortSignatureMutation.return_url!! requestMap["merchant_reference"] = merchantRef // requestMap["merchant_identifier"] = payfortSignatureMutation.merchant_identifier!! // requestMap["signature"] = payfortSignatureMutation.signature!! requestMap["sdk_token"] = sdkToken // requestMap["access_code"] = payfortSignatureMutation.access_code!! return requestMap } }