import React, { useState, useEffect } from 'react'; import { View, StyleSheet } from 'react-native'; import { initStripe, useConfirmPayment } from '@stripe/stripe-react-native'; import { Fade, Placeholder, PlaceholderLine } from 'rn-placeholder'; import { Checkout as CheckoutController, useOrder, useSession, useApi, useLanguage, useUtils, useValidationFields, useConfig, useToast, ToastType, } from 'ordering-components/native'; import { useTheme } from 'styled-components/native'; import { OText, OIcon } from '../shared'; import { AddressDetails } from '../AddressDetails'; import { PaymentOptions } from '../PaymentOptions'; import { DriverTips } from '../DriverTips'; import { NotFoundSource } from '../NotFoundSource'; import { UserDetails } from '../UserDetails'; import { FloatingButton } from '../FloatingButton'; import { Container } from '../../layouts/Container'; import NavBar from '../NavBar'; import { OrderSummary } from '../OrderSummary'; import { ChContainer, ChSection, ChHeader, ChAddress, CHDropBtnWrapper, ChPaymethods, ChDriverTips, ChErrors, ChBusinessDetails, ChUserDetails, ChCart } from './styles'; import { getTypesText } from '../../utils'; 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 styles = StyleSheet.create({ btnBackArrow: { borderWidth: 0, backgroundColor: theme.colors.white, borderColor: theme.colors.white, shadowColor: theme.colors.white, display: 'flex', justifyContent: 'flex-start', paddingLeft: 0, }, pagePadding: { paddingLeft: 40, paddingRight: 40 } }) 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 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(orderState.options)); // }, []) useEffect(() => { if (cart?.products?.length === 0) { navigation?.canGoBack() && navigation.goBack(); } }, [cart?.products]) return ( <> navigation?.canGoBack() && navigation.goBack()} title={t('CHECKOUT', 'Checkout')} style={styles.pagePadding} /> navigation.navigate('OrderTypes', { configTypes: configTypes })} activeOpacity={0.7} disabled={loading} > {t(getTypesText(options?.type || 0), '')} navigation.navigate('MomentOption')} disabled={loading} > {options?.moment ? parseDate(options?.moment, { outputFormat: configs?.format_time?.value === '12' ? 'MM/DD hh:mma' : 'MM/DD HH:mm' }) : t('ASAP_ABBREVIATION', 'ASAP')} { (businessDetails?.loading || cartState.loading) && !businessDetails?.error && ( )} { !cartState.loading && businessDetails?.business && Object.values(businessDetails?.business).length > 0 && ( <> {t('BUSINESS_DETAILS', 'Business Details')} {businessDetails?.business?.name} {businessDetails?.business?.email} {businessDetails?.business?.cellphone} {businessDetails?.business?.address} )} {businessDetails?.error && businessDetails?.error?.length > 0 && ( {t('BUSINESS_DETAILS', 'Business Details')} )} {cartState.loading ? ( ) : ( )} {!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')} )} )} {/* { ( <> {businessName || businessDetails?.business?.name} {cart?.total >= 1 && parsePrice(cart?.total) || cartTotal >= 1 && parsePrice(cartTotal)} )} */} {(businessDetails?.loading || cartState.loading) ? ( ) : ( )} {!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')} )} {!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')} )} )} {!cartState.loading && cart && ( {cartsWithProducts && cart?.products?.length === 0 ? ( ) : ( <> {t('ORDER_SUMMARY', 'Order Summary')} )} )} {!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} disabled={loading || !cart?.valid || !paymethodSelected || placing || errorCash || cart?.subtotal < cart?.minimum} 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)}`) } btnRightValueShow btnRightValue={parsePrice(cart?.total)} /> )} ) } 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 ( <> ) }