import React, { useState, useEffect } from 'react'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; import { View, StyleSheet, TouchableOpacity, Animated } from 'react-native'; import { initStripe, useConfirmPayment } from '@stripe/stripe-react-native'; import { Checkout as CheckoutController, useOrder, useSession, useApi, useLanguage, useUtils, useValidationFields, useConfig, ToastType, useToast } from 'ordering-components/native'; import { OText, OButton, OIcon, OModal } from '../shared'; import { AddressDetails } from '../AddressDetails'; import { PaymentOptions } from '../PaymentOptions'; import { DriverTips } from '../DriverTips'; import { OrderSummary } from '../OrderSummary'; import { NotFoundSource } from '../NotFoundSource'; import { UserDetails } from '../UserDetails'; import { OrderTypeSelector } from '../OrderTypeSelector' import { ChContainer, ChSection, ChHeader, ChTotal, ChAddress, ChMoment, CHMomentWrapper, ChPaymethods, ChDriverTips, ChCart, ChErrors, ChBusinessDetails, ChUserDetails, DivideLine, PayActionCont } from './styles'; import { Fade, Placeholder, PlaceholderLine } from 'rn-placeholder'; import { FloatingButton } from '../FloatingButton'; import { Container } from '../../layouts/Container'; import { useTheme } from 'styled-components/native'; import NavBar from '../NavBar'; const mapConfigs = { mapZoom: 16, mapSize: { width: 640, height: 190 } }; const manageErrorsToShow = (array = []) => { let stringError = '' const list = Array.isArray(array) ? array : Object.values(array) list.map((item: any, i: number) => { stringError += (i + 1) === array.length ? `- ${item?.message || item}` : `- ${item?.message || item}\n` }) return stringError; } const CheckoutUI = (props: any) => { const { navigation, cart, errors, placing, cartState, businessDetails, paymethodSelected, handlePaymethodChange, handlerClickPlaceOrder, onNavigationRedirect, businessLogo, businessName, cartTotal } = props const theme = useTheme(); const style = StyleSheet.create({ btnBackArrow: { borderWidth: 0, backgroundColor: theme.colors.white, borderColor: theme.colors.white, shadowColor: theme.colors.white, display: 'flex', justifyContent: 'flex-start', paddingLeft: 0, width: 40 }, paddSection: { padding: 20 }, paddSectionH: { paddingHorizontal: 20 } }) const [, { showToast }] = useToast(); const [, t] = useLanguage(); const [{ user }] = useSession(); const [{ configs }] = useConfig(); const [{ parsePrice, parseDate }] = useUtils(); const [{ options, carts, loading }] = useOrder(); const [validationFields] = useValidationFields(); const [errorCash, setErrorCash] = useState(false); const [userErrors, setUserErrors] = useState([]); const [isUserDetailsEdit, setIsUserDetailsEdit] = useState(false); const [phoneUpdate, setPhoneUpdate] = useState(false); const [isOpenPaymethods, setOpenPaymethods] = useState(false); const configTypes = configs?.order_types_allowed?.value.split('|').map((value: any) => Number(value)) || [] const cartsWithProducts = carts && Object.values(carts).filter((cart: any) => cart.products.length) || null const handlePlaceOrder = () => { if (!userErrors.length) { handlerClickPlaceOrder && handlerClickPlaceOrder() return } let stringError = '' Object.values(userErrors).map((item: any, i: number) => { stringError += (i + 1) === userErrors.length ? `- ${item?.message || item}` : `- ${item?.message || item}\n` }) showToast(ToastType.Error, stringError) setIsUserDetailsEdit(true) } const checkValidationFields = () => { setUserErrors([]) const errors = [] const notFields = ['coupon', 'driver_tip', 'mobile_phone', 'address', 'zipcode', 'address_notes'] Object.values(validationFields?.fields?.checkout).map((field: any) => { if (field?.required && !notFields.includes(field.code)) { if (!user[field?.code]) { errors.push(t(`VALIDATION_ERROR_${field.code.toUpperCase()}_REQUIRED`, `The field ${field?.name} is required`)) } } }) if ( !user?.cellphone && validationFields?.fields?.checkout?.cellphone?.enabled && validationFields?.fields?.checkout?.cellphone?.required ) { errors.push(t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Phone number is required')) } if (phoneUpdate) { errors.push(t('NECESSARY_UPDATE_COUNTRY_PHONE_CODE', 'It is necessary to update your phone number')) } setUserErrors(errors) } const getPayIcon = (method: string) => { switch (method) { case 'cash': return theme.images.general.cash case 'card_delivery': return theme.images.general.carddelivery case 'paypal': return theme.images.general.paypal case 'stripe': return theme.images.general.stripe case 'stripe_direct': return theme.images.general.stripecc case 'stripe_connect': return theme.images.general.stripes case 'stripe_redirect': return theme.images.general.stripesb default: return theme.images.general.creditCard } } const togglePhoneUpdate = (val: boolean) => { setPhoneUpdate(val) } useEffect(() => { if (validationFields && validationFields?.fields?.checkout) { checkValidationFields() } }, [validationFields, user]) useEffect(() => { if (errors) { const errorText = manageErrorsToShow(errors) showToast(ToastType.Error, errorText) } }, [errors]) return ( <> navigation?.canGoBack() && navigation.goBack()} /> {`${businessName || businessDetails?.business?.name} ${t('ORDER', 'Order')}`} {(businessDetails?.loading || cartState.loading) ? ( ) : ( )} navigation.navigate('MomentOption')} > {options?.moment ? parseDate(options?.moment, { outputFormat: configs?.format_time?.value === '12' ? 'MM/DD hh:mma' : 'MM/DD HH:mm' }) : t('ASAP_ABBREVIATION', 'ASAP')} {!cartState.loading && (cart?.status === 2 || cart?.status === 4) && ( {!cartState.loading && cart?.status === 2 && ( {t('CART_STATUS_PENDING_MESSAGE_APP', 'Your order is being processed, please wait a little more. if you\'ve been waiting too long, please reload the app')} )} )} {cartState.loading ? ( ) : ( )} {cartState.loading && ( )} {!cartState.loading && cart && cart?.status !== 2 && cart?.valid && ( <> {t('PAYMENT_METHOD', 'Payment Method')} {!cartState.loading && cart?.status === 4 && ( {t('CART_STATUS_CANCEL_MESSAGE', 'The payment has not been successful, please try again')} )} setOpenPaymethods(true)} activeOpacity={0.7}> {paymethodSelected ? ( <> {paymethodSelected?.data?.last4 ? ( {`${paymethodSelected?.paymethod?.name} \u2022\u2022\u2022 ${paymethodSelected.data.last4}`} ) : ( {`${paymethodSelected?.paymethod?.name}`} )} ) : ( {t('SELECT_PAYMENT_METHOD', 'Please select a payment method')} )} setOpenPaymethods(true)} activeOpacity={0.7}> )} {!cartState.loading && cart && ( {cartsWithProducts && cart?.products?.length === 0 ? ( ) : ( <> {`${cart?.products?.length} ${t('ITEMS', 'Items')}`} )} )} {!cartState.loading && cart && ( {!cart?.valid_address && cart?.status !== 2 && ( {t('INVALID_CART_ADDRESS', 'Selected address is invalid, please select a closer address.')} )} {!paymethodSelected && cart?.status !== 2 && cart?.valid && ( {t('WARNING_NOT_PAYMENT_SELECTED', 'Please, select a payment method to place order.')} )} {!cart?.valid_products && cart?.status !== 2 && ( {t('WARNING_INVALID_PRODUCTS', 'Some products are invalid, please check them.')} )} )} {!cartState.loading && cart && cart?.status !== 2 && ( <> <> handlePlaceOrder()} isSecondaryBtn={loading || !cart?.valid || !paymethodSelected || placing || errorCash || cart?.subtotal < cart?.minimum || paymethodSelected?.gateway === 'paypal'} disabled={loading || !cart?.valid || !paymethodSelected || placing || errorCash || cart?.subtotal < cart?.minimum || paymethodSelected?.gateway === 'paypal'} btnText={cart?.subtotal >= cart?.minimum ? ( placing ? t('PLACING', 'Placing') : loading ? t('LOADING', 'Loading') : t('PLACE_ORDER', 'Place Order') ) : (`${t('MINIMUN_SUBTOTAL_ORDER', 'Minimum subtotal order:')} ${parsePrice(cart?.minimum)}`) } /> )} setOpenPaymethods(false)} > setOpenPaymethods(false)} /> setOpenPaymethods(false)} /> } /> ) } export const Checkout = (props: any) => { const { errors, clearErrors, cartUuid, stripePaymentOptions, onNavigationRedirect, } = props const [, { showToast }] = useToast(); const [, t] = useLanguage(); const [{ token }] = useSession(); const [ordering] = useApi(); const [, { confirmCart }] = useOrder(); const { confirmPayment, loading: confirmPaymentLoading } = useConfirmPayment(); const [cartState, setCartState] = useState({ loading: true, error: [], cart: null }); const [orderState] = useOrder() const getOrder = async (cartId: any) => { try { setCartState({ ...cartState, loading: true }) const url = `${ordering.root}/carts/${cartId}` const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` } }) const { result } = await response.json(); let publicKey = null try { const { content } = await ordering.setAccessToken(token).paymentCards().getCredentials(); if (!content.error) { publicKey = content.result.publishable; } } catch (error) { publicKey = null } if (result.status === 1 && result.order?.uuid) { onNavigationRedirect('OrderDetails', { orderId: result.order.uuid }) setCartState({ ...cartState, loading: false }) } else if (result.status === 2 && result.paymethod_data?.gateway === 'stripe_redirect') { try { const confirmCartRes = await confirmCart(cartUuid) if (confirmCartRes.error) { showToast(ToastType.Error, confirmCartRes.error.message) } if (confirmCartRes.result.order?.uuid) { onNavigationRedirect('OrderDetails', { orderId: confirmCartRes.result.order.uuid, isFromCheckout: true }) } setCartState({ ...cartState, loading: false, cart: result }) } catch (error: any) { showToast(ToastType.Error, error?.toString() || error.message) } } else if (result.status === 2 && stripePaymentOptions.includes(result.paymethod_data?.gateway)) { const clientSecret = result.paymethod_data?.result?.client_secret const paymentMethodId = result.paymethod_data?.data?.source_id; initStripe({ publishableKey: publicKey }); try { const { paymentIntent, error } = await confirmPayment(clientSecret, { type: 'Card', paymentMethodId }); try { const confirmCartRes = await confirmCart(cartUuid) if (confirmCartRes.error) { showToast(ToastType.Error, confirmCartRes.error.message) setCartState({ ...cartState, loading: false, cart: result }) return } if (confirmCartRes.result.order?.uuid) { onNavigationRedirect('OrderDetails', { orderId: confirmCartRes.result.order.uuid, isFromCheckout: true }) setCartState({ ...cartState, loading: false }) } else { showToast(ToastType.Error, t('FAILED_PAYMENT', 'The payment has failed')); const cart = Array.isArray(result) ? null : result setCartState({ ...cartState, loading: false, cart, error: cart ? null : result }) return } } catch (error: any) { showToast(ToastType.Error, error?.toString() || error.message) } } catch (error) { showToast(ToastType.Error, t('FAILED_PAYMENT', 'The payment has failed')); const cart = Array.isArray(result) ? null : result setCartState({ ...cartState, loading: false, cart, error: cart ? null : result }) } } else { const cart = Array.isArray(result) ? null : result setCartState({ ...cartState, loading: false, cart, error: cart ? null : result }) } } catch (e: any) { setCartState({ ...cartState, loading: false, error: [e.toString()] }) } } useEffect(() => { if (errors) { const errorText = manageErrorsToShow(errors) showToast(ToastType.Error, errorText) clearErrors && clearErrors() } }, [errors]) useEffect(() => { if (token && cartUuid) { getOrder(cartUuid) } }, [token, cartUuid]) const checkoutProps = { ...props, UIComponent: CheckoutUI, cartState, businessId: cartState.cart?.business_id } return ( <> ) }