From ec7ff2452a1c63d62948a3014eccd8175cd0d5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20R=C3=B8svik?= Date: Wed, 27 Oct 2021 15:35:58 +0200 Subject: [PATCH] feat: add payment error message (#1712) --- src/components/message-box/index.tsx | 15 +++++---- src/screens/Profile/DesignSystem.tsx | 2 +- .../Ticketing/Purchase/Confirmation/index.tsx | 4 ++- .../Ticketing/Purchase/Overview/index.tsx | 4 ++- src/screens/Ticketing/Tickets/Tabs.tsx | 2 ++ .../Ticketing/Tickets/TicketsScrollView.tsx | 23 ++++++++++++- src/tickets/TicketContext.tsx | 33 +++++++++++++++++++ src/translations/screens/Tickets.ts | 5 +++ 8 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/components/message-box/index.tsx b/src/components/message-box/index.tsx index 330b9aad81..29113e8d04 100644 --- a/src/components/message-box/index.tsx +++ b/src/components/message-box/index.tsx @@ -14,12 +14,14 @@ import {useTranslation} from '@atb/translations'; type WithMessage = { message: string; - retryFunction?: () => void; + onPress?: () => void; + onPressText?: string; children?: never; }; type WithChildren = { message?: never; - retryFunction?: never; + onPress?: never; + onPressText?: string; children: React.ReactNode; }; export type MessageType = Statuses; @@ -39,7 +41,8 @@ const MessageBox: React.FC = ({ children, title, withMargin = false, - retryFunction, + onPress, + onPressText = 'OK', }) => { const {theme} = useTheme(); const styles = useBoxStyle(); @@ -56,13 +59,13 @@ const MessageBox: React.FC = ({ {message} - {retryFunction && ( + {onPress && ( - {t(MessageBoxTexts.tryAgainButton)} + {onPressText} )} diff --git a/src/screens/Profile/DesignSystem.tsx b/src/screens/Profile/DesignSystem.tsx index 9c5cc70b34..3505b8dd10 100644 --- a/src/screens/Profile/DesignSystem.tsx +++ b/src/screens/Profile/DesignSystem.tsx @@ -72,7 +72,7 @@ export default function DesignSystem() { message="This is an error with retry link" title="Title" type="error" - retryFunction={presser} + onPress={presser} /> diff --git a/src/screens/Ticketing/Purchase/Confirmation/index.tsx b/src/screens/Ticketing/Purchase/Confirmation/index.tsx index 3f9c42ddac..52a0f7e7ba 100644 --- a/src/screens/Ticketing/Purchase/Confirmation/index.tsx +++ b/src/screens/Ticketing/Purchase/Confirmation/index.tsx @@ -32,6 +32,7 @@ import {SelectPaymentMethod} from '../Payment'; import {CardPaymentMethod, PaymentMethod, SavedPaymentOption} from '../types'; import {useAuthState} from '@atb/auth'; import {usePreviousPaymentMethod} from '../saved-payment-utils'; +import MessageBoxTexts from '@atb/translations/components/MessageBox'; export type RouteParams = { preassignedFareProduct: PreassignedFareProduct; @@ -222,7 +223,8 @@ const Confirmation: React.FC = ({ type="error" title={t(PurchaseConfirmationTexts.errorMessageBox.title)} message={t(PurchaseConfirmationTexts.errorMessageBox.message)} - retryFunction={refreshOffer} + onPress={refreshOffer} + onPressText={t(MessageBoxTexts.tryAgainButton)} containerStyle={styles.errorMessage} /> )} diff --git a/src/screens/Ticketing/Purchase/Overview/index.tsx b/src/screens/Ticketing/Purchase/Overview/index.tsx index 43b4121abb..c162a64778 100644 --- a/src/screens/Ticketing/Purchase/Overview/index.tsx +++ b/src/screens/Ticketing/Purchase/Overview/index.tsx @@ -39,6 +39,7 @@ import FullScreenHeader from '@atb/components/screen-header/full-header'; import TravellersSheet from '@atb/screens/Ticketing/Purchase/Travellers/TravellersSheet'; import TravelDateSheet from '@atb/screens/Ticketing/Purchase/TravelDate/TravelDateSheet'; import {useTicketState} from '@atb/tickets'; +import MessageBoxTexts from '@atb/translations/components/MessageBox'; export type OverviewProps = { navigation: DismissableStackNavigationProp< @@ -161,7 +162,8 @@ const PurchaseOverview: React.FC = ({ type="error" title={t(PurchaseOverviewTexts.errorMessageBox.title)} message={t(PurchaseOverviewTexts.errorMessageBox.message)} - retryFunction={refreshOffer} + onPress={refreshOffer} + onPressText={t(MessageBoxTexts.tryAgainButton)} containerStyle={styles.errorMessage} /> )} diff --git a/src/screens/Ticketing/Tickets/Tabs.tsx b/src/screens/Ticketing/Tickets/Tabs.tsx index 7181432c60..21c882e90f 100644 --- a/src/screens/Ticketing/Tickets/Tabs.tsx +++ b/src/screens/Ticketing/Tickets/Tabs.tsx @@ -150,6 +150,7 @@ export const ActiveTickets: React.FC = () => { isRefreshingTickets, refreshTickets, customerProfile, + didPaymentFail, } = useTicketState(); const activeFareContracts = filterActiveOrCanBeUsedFareContracts( @@ -178,6 +179,7 @@ export const ActiveTickets: React.FC = () => { noTicketsLabel={t(TicketsTexts.activeTicketsTab.noTickets)} now={now} travelCard={customerProfile?.travelcard} + didPaymentFail={didPaymentFail} /> ); diff --git a/src/screens/Ticketing/Tickets/TicketsScrollView.tsx b/src/screens/Ticketing/Tickets/TicketsScrollView.tsx index dbb8e10b6c..e7f0b603c9 100644 --- a/src/screens/Ticketing/Tickets/TicketsScrollView.tsx +++ b/src/screens/Ticketing/Tickets/TicketsScrollView.tsx @@ -2,7 +2,12 @@ import ThemeText from '@atb/components/text'; import ErrorBoundary from '@atb/error-boundary'; import {RootStackParamList} from '@atb/navigation'; import {StyleSheet, useTheme} from '@atb/theme'; -import {ActiveReservation, FareContract, TravelCard} from '@atb/tickets'; +import { + ActiveReservation, + FareContract, + TravelCard, + useTicketState, +} from '@atb/tickets'; import {TicketsTexts, useTranslation} from '@atb/translations'; import {NavigationProp, useNavigation} from '@react-navigation/native'; import {isFuture} from 'date-fns'; @@ -14,6 +19,7 @@ import LinearGradient from 'react-native-linear-gradient'; import SimpleTicket from '../Ticket'; import TicketReservation from './TicketReservation'; import TravelCardInformation from './TravelCardInformation'; +import MessageBox from '@atb/components/message-box'; type RootNavigationProp = NavigationProp; @@ -25,6 +31,7 @@ type Props = { refreshTickets: () => void; now: number; travelCard?: TravelCard; + didPaymentFail?: boolean; }; const TicketsScrollView: React.FC = ({ @@ -35,11 +42,13 @@ const TicketsScrollView: React.FC = ({ refreshTickets, now, travelCard, + didPaymentFail = false, }) => { const {theme} = useTheme(); const styles = useStyles(); const navigation = useNavigation(); const {t} = useTranslation(); + const {resetPaymentStatus} = useTicketState(); const hasActiveTravelCard = !!travelCard; @@ -59,6 +68,15 @@ const TicketsScrollView: React.FC = ({ travelCard={travelCard} > )} + {didPaymentFail && ( + + )} {!fareContracts?.length && !reservations?.length && ( {noTicketsLabel} )} @@ -106,6 +124,9 @@ const useStyles = StyleSheet.createThemeHook((theme) => ({ gradient: { backgroundColor: theme.colors.background_1.backgroundColor, }, + messageBox: { + marginBottom: theme.spacings.large, + }, })); export default TicketsScrollView; diff --git a/src/tickets/TicketContext.tsx b/src/tickets/TicketContext.tsx index 96648a9f39..ff24464ee0 100644 --- a/src/tickets/TicketContext.tsx +++ b/src/tickets/TicketContext.tsx @@ -22,6 +22,7 @@ type TicketReducerState = { isRefreshingTickets: boolean; errorRefreshingTickets: boolean; customerProfile: CustomerProfile | undefined; + didPaymentFail: boolean; }; type TicketReducerAction = @@ -39,6 +40,10 @@ type TicketReducerAction = | { type: 'UPDATE_RESERVATIONS'; activeReservations: ActiveReservation[]; + } + | { + type: 'UPDATE_PAYMENT_FAILED'; + didPaymentFail: boolean; }; type TicketReducer = ( @@ -105,6 +110,12 @@ const ticketReducer: TicketReducer = ( customerProfile: action.customerProfile, }; } + case 'UPDATE_PAYMENT_FAILED': { + return { + ...prevState, + didPaymentFail: action.didPaymentFail, + }; + } } }; @@ -112,6 +123,8 @@ type TicketState = { addReservation: (reservation: ActiveReservation) => void; refreshTickets: () => void; fareContracts: FareContract[]; + didPaymentFail: boolean; + resetPaymentStatus: () => void; findFareContractByOrderId: (id: string) => FareContract | undefined; } & Pick< TicketReducerState, @@ -124,6 +137,7 @@ const initialReducerState: TicketReducerState = { isRefreshingTickets: false, errorRefreshingTickets: false, customerProfile: undefined, + didPaymentFail: false, }; const TicketContext = createContext(undefined); @@ -238,6 +252,17 @@ const TicketContextProvider: React.FC = ({children}) => { }), ); + if ( + updatedReservations.some( + ({paymentStatus}) => paymentStatus === 'REJECT', + ) + ) { + dispatch({ + type: 'UPDATE_PAYMENT_FAILED', + didPaymentFail: true, + }); + } + dispatch({ type: 'UPDATE_RESERVATIONS', activeReservations: updatedReservations.filter( @@ -248,6 +273,13 @@ const TicketContextProvider: React.FC = ({children}) => { [activeReservations, getPaymentStatus], ); + const resetPaymentStatus = () => { + dispatch({ + type: 'UPDATE_PAYMENT_FAILED', + didPaymentFail: false, + }); + }; + useInterval( pollPaymentStatus, 500, @@ -263,6 +295,7 @@ const TicketContextProvider: React.FC = ({children}) => { activeReservations, refreshTickets, addReservation, + resetPaymentStatus, findFareContractByOrderId: (orderId) => state.fareContracts.find((fc) => fc.orderId === orderId), }} diff --git a/src/translations/screens/Tickets.ts b/src/translations/screens/Tickets.ts index a2fc36de47..e85538cc09 100644 --- a/src/translations/screens/Tickets.ts +++ b/src/translations/screens/Tickets.ts @@ -99,6 +99,11 @@ const TicketsTexts = { `Noe gikk feil når vi prøvde å laste inn billett med ordre-id ${orderId}`, `Something went wrong when we tried to load ticket with order id ${orderId}`, ), + paymentError: _( + 'Betalingen mislyktes. Sjekk om du har tilstrekkelige midler på konto eller prøv et annet betalingsmiddel.', + 'Payment failed. Please check that you have sufficient funds on your account or try a different payment option.', + ), + paymentErrorButton: _('Lukk', 'Close'), }, travelCardInformation: { reisebevis: _('Reisebevis', 'Travel token'),