import React, { useState, useEffect } from 'react'; import { View, TextStyle, TouchableOpacity, Animated, Platform } 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 { OButton, OIcon, OModal, OText } from '../shared'; import { useTheme } from 'styled-components/native'; 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 { ChContainer, ChSection, ChAddress, ChPaymethods, ChDriverTips, ChCart, ChErrors, ChBusinessDetails, ChUserDetails } from './styles'; import { Fade, Placeholder, PlaceholderLine } from 'rn-placeholder'; import { FloatingButton } from '../FloatingButton'; import { Container } from '../../layouts/Container'; import NavBar from '../NavBar'; import { SafeAreaView } from 'react-native-safe-area-context'; 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 [, { 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 driverTipsOptions = typeof configs?.driver_tip_options?.value === 'string' ? JSON.parse(configs?.driver_tip_options?.value) || [] : configs?.driver_tip_options?.value || [] 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 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]) useEffect(() => { console.log(JSON.stringify(businessDetails.business)) }, []) // useEffect(() => { // handlePaymethodChange(null) // }, [cart?.total]) return ( navigation?.canGoBack() && navigation.goBack()} /> {!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')} )} )} {(businessDetails?.loading || cartState.loading) ? ( ) : ( {t('DELIVERY_DETAILS', 'Delivery details').toUpperCase()} )} {cartState.loading ? ( ) : ( )} { (businessDetails?.loading || cartState.loading) && !businessDetails?.error && ( )} { !cartState.loading && businessDetails?.business && Object.values(businessDetails?.business).length > 0 && ( {t('BUSINESS_DETAILS', 'Business Details').toUpperCase()} {businessDetails?.business?.name} {businessDetails?.business?.email} {businessDetails?.business?.cellphone} {businessDetails?.business?.address} )} {businessDetails?.error && businessDetails?.error?.length > 0 && ( {t('BUSINESS_DETAILS', 'Business Details').toUpperCase()} )} {!cartState.loading && cart && cart?.status !== 2 && cart?.valid && ( {t('PAYMENT_METHOD', 'Payment Method').toUpperCase()} setOpenPaymethods(true)} style={{flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center'}} > {paymethodSelected ? ( <> {paymethodSelected?.data?.last4 ? ( {`${paymethodSelected?.paymethod?.name} \u2022\u2022\u2022 ${paymethodSelected.data.last4}`} ) : ( {`${paymethodSelected?.paymethod?.name}`} )} ) : ( {t('SELECT_PAYMETHOD', 'Select Paymethods')} )} {!cartState.loading && cart?.status === 4 && ( {t('CART_STATUS_CANCEL_MESSAGE', 'The payment has not been successful, please try again')} )} )} {!cartState.loading && cart && ( {cartsWithProducts && cart?.products?.length === 0 ? ( ) : ( <> {t('SUMMARY', 'Summary').toUpperCase()} {businessName || businessDetails?.business?.name} navigation.navigate('Business', { store: businessDetails?.business?.original?.id })}> {t('GO_TO_STORE', 'Go to store')} {' \u2022 '} {t('CLEAR_CART', 'Clear cart')} )} )} {!cartState.loading && cart && cart?.valid && options.type === 1 && cart?.status !== 2 && validationFields?.fields?.checkout?.driver_tip?.enabled && driverTipsOptions && driverTipsOptions?.length > 0 && ( {t('DRIVER_TIPS', 'Driver Tips').toUpperCase()} )} {!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 && !isOpenPaymethods && ( 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)} style={{paddingLeft: 0, paddingRight: 0}} /> 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 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 ( <> ) }