diff --git a/.changeset/dark-bars-feel.md b/.changeset/dark-bars-feel.md new file mode 100644 index 0000000000..e2494a3e63 --- /dev/null +++ b/.changeset/dark-bars-feel.md @@ -0,0 +1,5 @@ +--- +"@exactly/mobile": patch +--- + +♻️ use new kyc flow diff --git a/cspell.json b/cspell.json index 2b6154b67c..e3fa77ce04 100644 --- a/cspell.json +++ b/cspell.json @@ -100,6 +100,8 @@ "natspec", "nfmelendez", "nomiclabs", + "noopener", + "noreferrer", "nystrom", "oneline", "onesignal", @@ -192,4 +194,4 @@ "\\b(w|stat)?aBas\\w+\\b", "\\baOpt\\w+\\b" ] -} +} \ No newline at end of file diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index 8c7fa647a5..12c5bfe264 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -26,7 +26,7 @@ import SpendingLimits from "./SpendingLimits"; import VerificationFailure from "./VerificationFailure"; import { presentArticle } from "../../utils/intercom"; import openBrowser from "../../utils/openBrowser"; -import { createInquiry, KYC_TEMPLATE_ID, resumeInquiry } from "../../utils/persona"; +import { startKYC } from "../../utils/persona"; import queryClient from "../../utils/queryClient"; import reportError from "../../utils/reportError"; import { @@ -98,13 +98,16 @@ export default function Card() { isFetching: isFetchingKYC, } = useQuery({ queryKey: ["kyc", "status"], - queryFn: async () => getKYCStatus(KYC_TEMPLATE_ID), + queryFn: async () => getKYCStatus(), meta: { suppressError: (error) => error instanceof APIError && - (error.text === "kyc not found" || error.text === "kyc not started" || error.text === "kyc not approved"), + (error.text === "no kyc" || error.text === "not started" || error.text === "bad kyc"), }, }); + const isKYCApproved = Boolean( + KYCStatus && "code" in KYCStatus && (KYCStatus.code === "ok" || KYCStatus.code === "legacy kyc"), + ); const { data: bytecode } = useBytecode({ address: address ?? zeroAddress, query: { enabled: !!address } }); const { refetch: refetchInstalledPlugins, isFetching: isFetchingPlugins } = useReadUpgradeableModularAccountGetInstalledPlugins({ @@ -159,7 +162,6 @@ export default function Card() { return; } if (isRevealing) return; - if (!credential) return; try { const { data, error } = await refetchCard(); if (error && error instanceof APIError && error.code === 500) throw error; @@ -167,31 +169,40 @@ export default function Card() { queryClient.setQueryData(["card-details-open"], true); return; } - const result = await getKYCStatus(KYC_TEMPLATE_ID); - if (result === "ok") { + const status = await getKYCStatus(); + if ("code" in status && (status.code === "ok" || status.code === "legacy kyc")) { setDisclaimerShown(true); return; } - if (typeof result !== "string") await resumeInquiry(result.inquiryId, result.sessionToken); } catch (error) { if (!(error instanceof APIError)) { reportError(error); return; } const { text } = error; - if (text === "kyc not approved") { + if (text === "bad kyc") { setVerificationFailureShown(true); return; } - if (text === "kyc required" || text === "kyc not found" || text === "kyc not started") { - await createInquiry(credential); + if (text !== "not started" && text !== "no kyc") { + reportError(error); + toast.show(t("An error occurred. Please try again later."), { + native: true, + duration: 1000, + burntOptions: { haptic: "error", preset: "error" }, + }); + return; } - reportError(error); + } + try { + await startKYC(); + } catch (error) { toast.show(t("An error occurred. Please try again later."), { native: true, duration: 1000, burntOptions: { haptic: "error", preset: "error" }, }); + reportError(error); } }, }); @@ -291,7 +302,7 @@ export default function Card() { - {(usdBalance === 0n || KYCStatus !== "ok") && ( + {(usdBalance === 0n || !isKYCApproved) && ( ({ queryKey: ["credential"] }); - const { mutateAsync: startKYC } = useMutation({ - mutationKey: ["kyc"], - mutationFn: async () => { - if (!credential) throw new Error("missing credential"); - try { - const result = await getKYCStatus(KYC_TEMPLATE_ID); - if (result === "ok") return; - if (typeof result !== "string") { - await resumeInquiry(result.inquiryId, result.sessionToken); - } - } catch (error) { - if (!(error instanceof APIError)) { - reportError(error); - return; - } - if (error.text === "kyc required" || error.text === "kyc not found" || error.text === "kyc not started") { - await createInquiry(credential); - return; - } - reportError(error); - } - }, - onSettled: async () => { - await queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }); - }, - }); + const { mutate: beginKYC } = useBeginKYC(); function handleAction() { switch (currentStep?.id) { case "add-funds": router.push("/add-funds/add-crypto"); break; case "verify-identity": - startKYC().catch(reportError); + beginKYC(); break; } } diff --git a/src/components/home/GettingStarted.tsx b/src/components/home/GettingStarted.tsx index b5a338a18c..015a53d79e 100644 --- a/src/components/home/GettingStarted.tsx +++ b/src/components/home/GettingStarted.tsx @@ -7,49 +7,16 @@ import { useRouter } from "expo-router"; import { ArrowRight, ChevronRight, IdCard } from "@tamagui/lucide-icons"; import { Spinner, XStack, YStack } from "tamagui"; -import { useMutation, useQuery } from "@tanstack/react-query"; - -import { createInquiry, KYC_TEMPLATE_ID, resumeInquiry } from "../../utils/persona"; -import queryClient from "../../utils/queryClient"; -import reportError from "../../utils/reportError"; -import { APIError, getKYCStatus } from "../../utils/server"; +import useBeginKYC from "../../utils/useBeginKYC"; import useOnboardingSteps from "../../utils/useOnboardingSteps"; import Text from "../shared/Text"; import View from "../shared/View"; -import type { Credential } from "@exactly/common/validation"; - export default function GettingStarted({ isDeployed, hasKYC }: { hasKYC: boolean; isDeployed: boolean }) { const router = useRouter(); const { t } = useTranslation(); const { currentStep, completedSteps, setSteps } = useOnboardingSteps(); - const { data: credential } = useQuery({ queryKey: ["credential"] }); - const { mutateAsync: startKYC, isPending } = useMutation({ - mutationKey: ["kyc"], - mutationFn: async () => { - if (!credential) throw new Error("missing credential"); - try { - const result = await getKYCStatus(KYC_TEMPLATE_ID); - if (result === "ok") return; - if (typeof result !== "string") { - resumeInquiry(result.inquiryId, result.sessionToken).catch(reportError); - } - } catch (error) { - if (!(error instanceof APIError)) { - reportError(error); - return; - } - if (error.text === "kyc required" || error.text === "kyc not found" || error.text === "kyc not started") { - await createInquiry(credential); - return; - } - reportError(error); - } - }, - onSettled: async () => { - await queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }); - }, - }); + const { mutate: beginKYC, isPending } = useBeginKYC(); function handleStepPress() { if (isPending) return; switch (currentStep?.id) { @@ -57,7 +24,7 @@ export default function GettingStarted({ isDeployed, hasKYC }: { hasKYC: boolean router.push("/add-funds/add-crypto"); break; case "verify-identity": - startKYC().catch(reportError); + beginKYC(); break; } } diff --git a/src/components/home/Home.tsx b/src/components/home/Home.tsx index 3a851fa281..7c61b82e07 100644 --- a/src/components/home/Home.tsx +++ b/src/components/home/Home.tsx @@ -29,7 +29,6 @@ import PortfolioSummary from "./PortfolioSummary"; import SpendingLimitsSheet from "./SpendingLimitsSheet"; import VisaSignatureBanner from "./VisaSignatureBanner"; import VisaSignatureModal from "./VisaSignatureSheet"; -import { KYC_TEMPLATE_ID, LEGACY_KYC_TEMPLATE_ID } from "../../utils/persona"; import queryClient from "../../utils/queryClient"; import reportError from "../../utils/reportError"; import { APIError, getActivity, getKYCStatus, type CardDetails } from "../../utils/server"; @@ -100,27 +99,17 @@ export default function Home() { refetch: refetchKYCStatus, } = useQuery({ queryKey: ["kyc", "status"], - queryFn: async () => getKYCStatus(KYC_TEMPLATE_ID), + queryFn: async () => getKYCStatus(), meta: { suppressError: (error) => error instanceof APIError && - (error.text === "kyc not found" || error.text === "kyc not started" || error.text === "kyc not approved"), - }, - }); - const { - data: legacyKYCStatus, - isFetched: isLegacyKYCFetched, - refetch: refetchLegacyKYCStatus, - } = useQuery({ - queryKey: ["legacy", "kyc", "status"], - queryFn: async () => getKYCStatus(LEGACY_KYC_TEMPLATE_ID), - enabled: isKYCFetched && KYCStatus !== "ok", - meta: { - suppressError: (error) => - error instanceof APIError && - (error.text === "kyc not found" || error.text === "kyc not started" || error.text === "kyc not approved"), + (error.text === "no kyc" || error.text === "not started" || error.text === "bad kyc"), }, }); + const needsMigration = Boolean(KYCStatus && "code" in KYCStatus && KYCStatus.code === "legacy kyc"); + const isKYCApproved = Boolean( + KYCStatus && "code" in KYCStatus && (KYCStatus.code === "ok" || KYCStatus.code === "legacy kyc"), + ); const { data: card } = useQuery({ queryKey: ["card", "details"], enabled: !!account && !!bytecode }); const scrollRef = useRef(null); @@ -129,7 +118,6 @@ export default function Home() { refetchBytecode().catch(reportError); refetchMarkets().catch(reportError); refetchKYCStatus().catch(reportError); - refetchLegacyKYCStatus().catch(reportError); refetchPendingProposals().catch(reportError); }; useTabPress("index", () => { @@ -138,6 +126,8 @@ export default function Home() { }); const isPending = isPendingActivity || isPendingPreviewer; + const showKycMigration = isKYCFetched && needsMigration; + const showPluginOutdated = !!bytecode && !!installedPlugins && !isLatestPlugin; return ( @@ -153,8 +143,7 @@ export default function Home() { {markets && healthFactor(markets) < HEALTH_FACTOR_THRESHOLD && } - {((isKYCFetched && isLegacyKYCFetched && legacyKYCStatus === "ok" && KYCStatus !== "ok") || - (!!bytecode && !!installedPlugins && !isLatestPlugin)) && ( + {(showKycMigration || showPluginOutdated) && ( )} - - {KYCStatus === "ok" && } + + {isKYCApproved && } { router.setParams({ ...parameters, maturity: String(maturity) }); diff --git a/src/components/home/card-upgrade/VerifyIdentity.tsx b/src/components/home/card-upgrade/VerifyIdentity.tsx index 78028e8d3c..4e1989b6dc 100644 --- a/src/components/home/card-upgrade/VerifyIdentity.tsx +++ b/src/components/home/card-upgrade/VerifyIdentity.tsx @@ -5,10 +5,10 @@ import { IdCard } from "@tamagui/lucide-icons"; import { useToastController } from "@tamagui/toast"; import { Spinner, YStack } from "tamagui"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation } from "@tanstack/react-query"; import Progression from "./Progression"; -import { createInquiry, KYC_TEMPLATE_ID, resumeInquiry } from "../../../utils/persona"; +import { startKYC } from "../../../utils/persona"; import queryClient from "../../../utils/queryClient"; import reportError from "../../../utils/reportError"; import { APIError, getKYCStatus } from "../../../utils/server"; @@ -16,41 +16,32 @@ import Button from "../../shared/Button"; import Text from "../../shared/Text"; import View from "../../shared/View"; -import type { Credential } from "@exactly/common/validation"; - export default function VerifyIdentity() { const toast = useToastController(); const { t } = useTranslation(); - const { data: credential } = useQuery({ queryKey: ["credential"] }); - const { mutateAsync: startKYC, isPending } = useMutation({ + const { mutate: beginKYC, isPending } = useMutation({ mutationKey: ["kyc"], - mutationFn: async () => { - if (!credential) throw new Error("missing credential"); + async mutationFn() { try { - const result = await getKYCStatus(KYC_TEMPLATE_ID); - if (result === "ok") { + const status = await getKYCStatus(); + if ("code" in status && (status.code === "ok" || status.code === "legacy kyc")) { queryClient.setQueryData(["card-upgrade"], 1); return; } - if (typeof result !== "string") { - await resumeInquiry(result.inquiryId, result.sessionToken); - } } catch (error) { if (!(error instanceof APIError)) { - reportError(error); - return; + throw error; } - if (error.text === "kyc required" || error.text === "kyc not found" || error.text === "kyc not started") { - await createInquiry(credential); - return; + if (error.text !== "not started" && error.text !== "no kyc") { + throw error; } - reportError(error); } + await startKYC(); }, - onSettled: async () => { + async onSettled() { await queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }); }, - onError: (error) => { + onError(error) { toast.show(t("Error verifying identity"), { native: true, duration: 1000, @@ -79,7 +70,7 @@ export default function VerifyIdentity() {