Skip to content

Commit

Permalink
fix: [IOBP-596] Fix unexpected behavior in wallet onboarding Webview …
Browse files Browse the repository at this point in the history
…on Android devices (#5594)

## Short description
This PR fixes a bug in the wallet onboarding flow, which was causing the
webview to reopen after the flow completed.

## List of changes proposed in this pull request
- Simplified types by leveraging the use of `io-ts` to ensure correct
runtime type checking.
- Refactored `useWalletOnboardingWebView` custom hook with simplified
logics
- Removed unnecessary components in the `WalletOnboardingFeedbackScreen`
which now renders directly the onboarding outcome.

## How to test
Using the
[io-dev-api-server](https://github.com/pagopa/io-dev-api-server) or the
UAT environment, navigate to the **Payments playground** within the
**Profile** section of the app.
Try to onboard new methods while checking that the Webivew does not
reopens after a successful (or unsuccessful) onboarding.

## Preview

<video
src="https://github.com/pagopa/io-app/assets/6160324/d0a8170d-4e1f-4e6b-91c5-6a965216bf42"
/>

---------

Co-authored-by: Alessandro Izzo <[email protected]>
  • Loading branch information
mastro993 and Hantex9 authored Mar 18, 2024
1 parent 892f473 commit 4e47fda
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 430 deletions.
15 changes: 8 additions & 7 deletions locales/de/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ permissionRationale:
torch:
title: "Zugriff auf die Kamera gewähren"
message: "Wir brauchen Zugang zur Kamera, um die Taschenlampenfunktion zu benutzen."
startup:
startup:
authentication: "Benutzer authentifiziert"
sessionInfo: "SPID-Attribute geladen"
profileInfo: "Benutzerprofil geladen"
Expand Down Expand Up @@ -556,8 +556,8 @@ authentication:
card2-title: "Verwende deine bevorzugten Zahlungsmethoden"
card2-content: "Du kannst eine Steuer oder eine Strafe über dein Bankkonto oder mit deiner Bankkarte bezahlen"
card3-title: "Bezahle schnell eine Aufforderung in Papierform"
card3-content: "Durch das Scannen des QR-Codes kannst du schnell und einfach eine Zahlungsaufforderung in Papierform bezahlen"
card5-title: "Willkommen in IO!"
card3-content: "Durch das Scannen des QR-Codes kannst du schnell und einfach eine Zahlungsaufforderung in Papierform bezahlen"
card5-title: "Willkommen in IO!"
card5-content: "Lass uns gemeinsam herausfinden, was du machen kannst"
card5-content-accessibility: "Wähle das vorherige Element, um einen Chat mit dem IO-Team zu beginnen, wenn etwas nicht so funktioniert, wie es sollte."
expiredCardTitle: "Karte abgelaufen oder nicht mehr gültig"
Expand Down Expand Up @@ -1159,10 +1159,11 @@ wallet:
title: "Ich weiß es nicht"
description: "Wir durchsuchen alle auf deinem Namen lautenden Karten bei den am Dienst teilnehmenden Banken"
generalError: "Beim Hinzufügen der Zahlungsmethode trat ein allgemeiner Fehler auf"
success:
title: "Die Karte wurde hinzugefügt!"
continueButton: "Weiter"
failure:
outcome:
SUCCESS:
title: "Die Karte wurde hinzugefügt!"
subtitle: ""
primaryAction: "Weiter"
GENERIC_ERROR:
title: "Es ist ein unerwarteter Fehler aufgetreten"
subtitle: "Versuche es erneut oder kontaktiere den Support"
Expand Down
9 changes: 5 additions & 4 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1203,10 +1203,11 @@ wallet:
title: "I don't know"
description: "We will search for all cards registered to you at participating banks"
generalError: "There was a generic error while adding the payment method"
success:
title: "Payment method added successfully!"
continueButton: "Vedi dettagli"
failure:
outcome:
SUCCESS:
title: "Payment method added successfully!"
subtitle: ""
primaryAction: "Vedi dettagli"
GENERIC_ERROR:
title: Si è verificato un errore imprevisto
subtitle: Riprova o contatta l'assistenza
Expand Down
9 changes: 5 additions & 4 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1203,10 +1203,11 @@ wallet:
title: "Non lo so"
description: "Cercheremo tutte le carte intestate a te presso le banche aderenti al servizio"
generalError: "C'è stato un errore generico durante l'aggiunta del metodo di pagamento"
success:
title: "Il metodo è stato aggiunto"
continueButton: "Vedi dettagli"
failure:
outcome:
SUCCESS:
title: "Il metodo è stato aggiunto"
subtitle: ""
primaryAction: "Vedi dettagli"
GENERIC_ERROR:
title: Si è verificato un errore imprevisto
subtitle: Riprova o contatta l'assistenza
Expand Down

This file was deleted.

This file was deleted.

171 changes: 82 additions & 89 deletions ts/features/payments/onboarding/hooks/useWalletOnboardingWebView.tsx
Original file line number Diff line number Diff line change
@@ -1,123 +1,116 @@
import * as React from "react";
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import * as pot from "@pagopa/ts-commons/lib/pot";
import { openAuthenticationSession } from "@pagopa/io-react-native-login-utils";
import {
WebViewErrorEvent,
WebViewHttpErrorEvent
} from "react-native-webview/lib/WebViewTypes";

import { OnboardingOutcomeFailure, OnboardingOutcomeSuccess } from "../types";
import { NetworkError } from "../../../../utils/errors";
import { WalletCreateResponse } from "../../../../../definitions/pagopa/walletv3/WalletCreateResponse";
import * as pot from "@pagopa/ts-commons/lib/pot";
import * as E from "fp-ts/lib/Either";
import * as TE from "fp-ts/lib/TaskEither";
import { pipe } from "fp-ts/lib/function";
import * as React from "react";
import URLParse from "url-parse";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import { walletOnboardingStartupSelector } from "../store";
import { walletStartOnboarding } from "../store/actions";
import {
ONBOARDING_CALLBACK_URL_SCHEMA,
extractOnboardingResult
} from "../utils";
WalletOnboardingOutcome,
WalletOnboardingOutcomeEnum
} from "../types/OnboardingOutcomeEnum";
import { ONBOARDING_CALLBACK_URL_SCHEMA } from "../utils";

/**
* Function that extracts the uri to be loaded in the webview from the onboarding startup result pot
*/
const extractOnboardingWebViewUri = (
onboardingStartupResult: pot.Pot<WalletCreateResponse, NetworkError>
) =>
pot.getOrElse(
pot.map(onboardingStartupResult, result => encodeURI(result.redirectUrl)),
""
);

export type WalletOnboardingWebViewProps = {
onSuccess?: (outcome: OnboardingOutcomeSuccess, walletId: string) => void;
onFailure?: (outcome: OnboardingOutcomeFailure) => void;
onError?: (
error?: WebViewErrorEvent | WebViewHttpErrorEvent | NetworkError
type WalletOnboardingWebViewProps = {
onOnboardingOutcome: (
outcome: WalletOnboardingOutcome,
walletId?: string
) => void;
onDismiss?: () => void;
};

export type WalletOnboardingWebView = {
isLoading: boolean;
isError: boolean;
isPendingOnboarding: boolean;
startOnboarding: (paymentMethodId: string) => void;
};

/**
* This hook handles the onboarding webview flow and returns a function to start the onboarding
* @param onSuccess callback called when the onboarding is successful
* @param onFailure callback called when the onboarding is failed
* @param onError callback called when an error occurs
* @param onOnboardingOutcome callback called when the onboarding flow is completed
*/
export const useWalletOnboardingWebView = (
props: WalletOnboardingWebViewProps
) => {
const { onSuccess, onError, onFailure, onDismiss } = props;
const [isLoadingWebView, setIsLoadingWebView] = React.useState(false);
const onboardingStartupResult = useIOSelector(
walletOnboardingStartupSelector
);
export const useWalletOnboardingWebView = ({
onOnboardingOutcome
}: WalletOnboardingWebViewProps): WalletOnboardingWebView => {
const dispatch = useIODispatch();

const handleResultOnboarding = React.useCallback(
(url: string) => {
pipe(
url,
extractOnboardingResult,
O.fromNullable,
O.map(result => {
if (result.status === "SUCCESS") {
onSuccess?.(
result.outcome as OnboardingOutcomeSuccess,
result.walletId
);
} else if (result.status === "FAILURE") {
onFailure?.(result.outcome as OnboardingOutcomeFailure);
}
})
const onboardingUrlPot = useIOSelector(walletOnboardingStartupSelector);

const [isPendingOnboarding, setIsPendingOnboarding] =
React.useState<boolean>(false);
const isLoading = pot.isLoading(onboardingUrlPot);
const isError = pot.isError(onboardingUrlPot);

const handleOnboardingResult = React.useCallback(
(resultUrl: string) => {
const url = new URLParse(resultUrl, true);

const outcome = pipe(
url.query.outcome,
WalletOnboardingOutcome.decode,
E.getOrElse(() => WalletOnboardingOutcomeEnum.GENERIC_ERROR)
);

onOnboardingOutcome(outcome, url.query.walletId);
},
[onSuccess, onFailure]
[onOnboardingOutcome]
);

const openOnboardingWebView = React.useCallback(async () => {
try {
const resultUrl = await openAuthenticationSession(
extractOnboardingWebViewUri(onboardingStartupResult),
ONBOARDING_CALLBACK_URL_SCHEMA
);
handleResultOnboarding(resultUrl);
} catch (err) {
onDismiss?.();
} finally {
setIsLoadingWebView(false);
React.useEffect(() => {
if (isPendingOnboarding) {
return;
}
}, [onboardingStartupResult, handleResultOnboarding, onDismiss]);

void pipe(
onboardingUrlPot,
pot.toOption,
TE.fromOption(() => undefined),
TE.chain(({ redirectUrl }) =>
TE.tryCatch(
() => {
setIsPendingOnboarding(true);
return openAuthenticationSession(
redirectUrl,
ONBOARDING_CALLBACK_URL_SCHEMA
);
},
() => {
onOnboardingOutcome(WalletOnboardingOutcomeEnum.CANCELED_BY_USER);
}
)
),
TE.map(handleOnboardingResult)
)();
}, [
isError,
isLoading,
isPendingOnboarding,
onboardingUrlPot,
handleOnboardingResult,
onOnboardingOutcome,
dispatch
]);

React.useEffect(
() => () => {
setIsPendingOnboarding(false);
dispatch(walletStartOnboarding.cancel());
},
[dispatch]
);

React.useEffect(() => {
if (pot.isError(onboardingStartupResult) && onError) {
onError(onboardingStartupResult.error);
}
if (
!pot.isError(onboardingStartupResult) &&
!pot.isLoading(onboardingStartupResult)
) {
void openOnboardingWebView();
}
}, [onboardingStartupResult, openOnboardingWebView, onError]);

const startOnboarding = (paymentMethodId: string) => {
if (!pot.isLoading(onboardingStartupResult) && !isLoadingWebView) {
setIsLoadingWebView(true);
dispatch(walletStartOnboarding.request({ paymentMethodId }));
}
setIsPendingOnboarding(false);
dispatch(walletStartOnboarding.request({ paymentMethodId }));
};

return {
startOnboarding,
isLoadingWebView
isLoading,
isError,
isPendingOnboarding
};
};
Loading

0 comments on commit 4e47fda

Please sign in to comment.