Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix segmented control and amex error in payment link #774

Merged
merged 7 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 102 additions & 54 deletions clients/payment/src/components/CardPayment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import { colors, radii, spacings } from "@swan-io/lake/src/constants/design";
import { useResponsive } from "@swan-io/lake/src/hooks/useResponsive";
import { showToast } from "@swan-io/lake/src/state/toasts";
import { filterRejectionsToResult } from "@swan-io/lake/src/utils/gql";
import { isNullish } from "@swan-io/lake/src/utils/nullish";
import { translateError } from "@swan-io/shared-business/src/utils/i18n";
import { FrameCardTokenizedEvent, Frames } from "frames-react";
import { useEffect, useState } from "react";
import { StyleSheet, View } from "react-native";
import { match } from "ts-pattern";
import { P, match } from "ts-pattern";
import {
AddCardPaymentMandateDocument,
GetMerchantPaymentLinkQuery,
Expand Down Expand Up @@ -56,12 +55,6 @@ type Props = {
publicKey: string;
};

type FrameEvent = {
element: "card-number" | "expiry-date" | "cvv";
isValid: boolean;
isEmpty: boolean;
};

export const CardPayment = ({ paymentLink, paymentMethodId, publicKey }: Props) => {
const { desktop } = useResponsive();

Expand All @@ -73,6 +66,8 @@ export const CardPayment = ({ paymentLink, paymentMethodId, publicKey }: Props)
type CardFieldState = FieldState | "cardNotSupported";

const [cardNumberState, setCardNumberState] = useState<CardFieldState>("untouched");
const [isPaymentMethodValid, setIsPaymentMethodValid] = useState<boolean>(true);

const [expiryDateState, setExpiryDateState] = useState<FieldState>("untouched");
const [cvvState, setCvvState] = useState<FieldState>("untouched");

Expand Down Expand Up @@ -104,52 +99,72 @@ export const CardPayment = ({ paymentLink, paymentMethodId, publicKey }: Props)
expiryYearPlaceholder: "YY",
},
schemeChoice: true,
acceptedPaymentMethods: ["Visa", "Maestro", "Mastercard", "Cartes Bancaires"],
});
}, [publicKey]);

useEffect(() => {
Frames.addEventHandler(
"paymentMethodChanged",
// @ts-expect-error addEventHandler isn't typed correctly
(event: unknown) => {
match(event)
.with({ isPaymentMethodAccepted: P.boolean }, ({ isPaymentMethodAccepted }) => {
setIsPaymentMethodValid(isPaymentMethodAccepted);
})
.otherwise(() => {});

const cardType = match(event)
.with({ paymentMethod: P.string }, ({ paymentMethod }) => paymentMethod)
.otherwise(() => {});

match(cardType?.toLowerCase())
.with("visa", "maestro", "mastercard", "cartes bancaires", paymentMethod => {
setPaymentMethod(Option.Some(paymentMethod));
})
.otherwise(() => {
setPaymentMethod(Option.None());
});
},
);

//@ts-expect-error addEventHandler isn't typed correctly
Frames.addEventHandler("frameValidationChanged", (event: FrameEvent) => {
match(event)
.with({ element: "card-number", isEmpty: true }, () => {
Frames.addEventHandler("frameValidationChanged", (event: unknown) => {
match({ event, cardNumberState })
.with({ event: { element: "card-number", isEmpty: true } }, () => {
setCardNumberState("empty");
})
.with({ element: "card-number", isValid: false }, () => {
.with({ event: { element: "card-number", isValid: false } }, () => {
setCardNumberState("invalid");
})
.with({ element: "card-number", isValid: true }, () => setCardNumberState("valid"))
.with({ element: "expiry-date", isEmpty: true }, () => {
.with(
{ event: { element: "card-number", isValid: true }, cardNumberState: "cardNotSupported" },
() => {
setCardNumberState("cardNotSupported");
},
)
.with({ event: { element: "card-number", isValid: true } }, () =>
setCardNumberState("valid"),
)
.with({ event: { element: "expiry-date", isEmpty: true } }, () => {
setExpiryDateState("empty");
})
.with({ element: "expiry-date", isValid: false }, () => {
.with({ event: { element: "expiry-date", isValid: false } }, () => {
setExpiryDateState("invalid");
})
.with({ element: "expiry-date", isValid: true }, () => setExpiryDateState("valid"))
.with({ element: "cvv", isEmpty: true }, () => {
.with({ event: { element: "expiry-date", isValid: true } }, () =>
setExpiryDateState("valid"),
)
.with({ event: { element: "cvv", isEmpty: true } }, () => {
setCvvState("empty");
})
.with({ element: "cvv", isValid: false }, () => {
.with({ event: { element: "cvv", isValid: false } }, () => {
setCvvState("invalid");
})
.with({ element: "cvv", isValid: true }, () => setCvvState("valid"))
.exhaustive();
});
//@ts-expect-error addEventHandler isn't typed correctly
Frames.addEventHandler("paymentMethodChanged", (event: { paymentMethod: string }) => {
const cardType = event.paymentMethod;
if (cardType === "American Express") {
setCardNumberState("cardNotSupported");
}

if (isNullish(cardType)) {
setPaymentMethod(Option.None());
} else {
match(cardType.toLowerCase())
.with("visa", "maestro", "mastercard", "cartes bancaires", paymentMethod =>
setPaymentMethod(Option.Some(paymentMethod)),
)
.otherwise(() => setPaymentMethod(Option.None()));
}
.with({ event: { element: "cvv", isValid: true } }, () => setCvvState("valid"))
.otherwise(() => {});
});
}, [publicKey]);
}, [cardNumberState]);

const onPressSubmit = () => {
if (cardNumberState === "untouched") {
Expand Down Expand Up @@ -207,35 +222,66 @@ export const CardPayment = ({ paymentLink, paymentMethodId, publicKey }: Props)
<Box direction="row" grow={1} shrink={1}>
<div
className={`card-number-frame ${paymentMethod.isSome() ? "card-number-frame-with-logo" : ""}`}
style={match(cardNumberState)
.with("invalid", "empty", "cardNotSupported", () => ({
borderColor: colors.negative[400],
}))
.with("untouched", "valid", () => undefined)
.exhaustive()}
style={match({ cardNumberState, isPaymentMethodValid })
.with(
{ cardNumberState: P.union("invalid", "empty", "cardNotSupported") },
{ isPaymentMethodValid: false },
() => ({
borderColor: colors.negative[400],
}),
)
.otherwise(() => undefined)}
>
{/* <!-- card number will be added here --> */}
</div>

{match(paymentMethod)
.with(Option.P.None, () => null)
.with(Option.P.Some("cartes bancaires"), () => (
<View style={styles.cardLogo}>
<View
style={[
styles.cardLogo,
(isPaymentMethodValid === false || cardNumberState !== "valid") && {
borderColor: colors.negative[500],
},
]}
>
<CarteBancaireLogo />
</View>
))
.with(Option.P.Some("maestro"), () => (
<View style={styles.cardLogo}>
<View
style={[
styles.cardLogo,
(isPaymentMethodValid === false || cardNumberState !== "valid") && {
borderColor: colors.negative[500],
},
]}
>
<MaestroLogo />
</View>
))
.with(Option.P.Some("mastercard"), () => (
<View style={styles.cardLogo}>
<View
style={[
styles.cardLogo,
(isPaymentMethodValid === false || cardNumberState !== "valid") && {
borderColor: colors.negative[500],
},
]}
>
<MastercardLogo />
</View>
))
.with(Option.P.Some("visa"), () => (
<View style={styles.cardLogo}>
<View
style={[
styles.cardLogo,
(isPaymentMethodValid === false || cardNumberState !== "valid") && {
borderColor: colors.negative[500],
},
]}
>
<VisaLogo />
</View>
))
Expand All @@ -244,12 +290,14 @@ export const CardPayment = ({ paymentLink, paymentMethodId, publicKey }: Props)

<Box direction="row" style={styles.errorContainer}>
<LakeText variant="smallRegular" color={colors.negative[500]}>
{match(cardNumberState)
.with("invalid", () => t("paymentLink.invalidCardNumber"))
.with("cardNotSupported", () => t("paymentLink.cardNotSupported"))
.with("empty", () => t("paymentLink.cardNumberRequired"))
.with("untouched", "valid", () => " ")
.exhaustive()}
{match({ cardNumberState, isPaymentMethodValid })
.with({ cardNumberState: "invalid" }, () => t("paymentLink.invalidCardNumber"))
.with({ cardNumberState: "cardNotSupported" }, () =>
t("paymentLink.cardNotSupported"),
)
.with({ cardNumberState: "empty" }, () => t("paymentLink.cardNumberRequired"))
.with({ isPaymentMethodValid: false }, () => t("paymentLink.cardNotSupported"))
.otherwise(() => " ")}
</LakeText>
</Box>
</>
Expand Down
6 changes: 3 additions & 3 deletions clients/payment/src/components/PaymentArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ export const PaymentArea = ({ paymentLinkId }: Props) => {
<>
<Box direction="row" alignItems="center">
<LakeButton
ariaLabel={t("common.back")}
icon="arrow-left-filled"
ariaLabel={t("common.cancel")}
icon="dismiss-regular"
mode="tertiary"
onPress={() => {
window.location.replace(cancelRedirectUrl);
}}
>
{desktop ? t("common.back") : null}
{desktop ? t("common.cancel") : null}
</LakeButton>

<Fill minWidth={16} />
Expand Down
2 changes: 1 addition & 1 deletion clients/payment/src/locales/de.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"button.pay": "Bezahlen",
"common.back": "Zurück",
"common.cancel": "Abbrechen",
"error.pageNotFound": "Seite nicht gefunden",
"form.invalidField": "Ungültiges Feld",
"paymentLink.addressLine1": "Adresszeile",
Expand Down
2 changes: 1 addition & 1 deletion clients/payment/src/locales/en.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"button.pay": "Pay",
"common.back": "Back",
"common.cancel": "Cancel",
"error.pageNotFound": "Page not found",
"form.invalidField": "Invalid field",
"paymentLink.addressLine1": "Address line",
Expand Down
2 changes: 1 addition & 1 deletion clients/payment/src/locales/es.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"button.pay": "Pagar",
"common.back": "Volver",
"common.cancel": "Cancelar",
"error.pageNotFound": "Página no encontrada",
"form.invalidField": "Campo inválido",
"paymentLink.addressLine1": "Dirección",
Expand Down
2 changes: 1 addition & 1 deletion clients/payment/src/locales/fi.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"button.pay": "Maksa",
"common.back": "Takaisin",
"common.cancel": "Peruuta",
"error.pageNotFound": "Sivua ei löytynyt",
"form.invalidField": "Virheellinen kenttä",
"paymentLink.addressLine1": "Katuosoite",
Expand Down
2 changes: 1 addition & 1 deletion clients/payment/src/locales/fr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"button.pay": "Payer",
"common.back": "Retour",
"common.cancel": "Annuler",
"error.pageNotFound": "Page non trouvée",
"form.invalidField": "Champ invalide",
"paymentLink.addressLine1": "Adresse",
Expand Down
2 changes: 1 addition & 1 deletion clients/payment/src/locales/it.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"button.pay": "Paga",
"common.back": "Indietro",
"common.cancel": "Annulla",
"error.pageNotFound": "Pagina non trovata",
"form.invalidField": "Campo non valido",
"paymentLink.addressLine1": "Indirizzo",
Expand Down
2 changes: 1 addition & 1 deletion clients/payment/src/locales/nl.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"button.pay": "Betalen",
"common.back": "Terug",
"common.cancel": "Annuleren",
"error.pageNotFound": "Pagina niet gevonden",
"form.invalidField": "Ongeldig veld",
"paymentLink.addressLine1": "Adresregel",
Expand Down
2 changes: 1 addition & 1 deletion clients/payment/src/locales/pt.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"button.pay": "Pagar",
"common.back": "Voltar",
"common.cancel": "Cancelar",
"error.pageNotFound": "Página não encontrada",
"form.invalidField": "Campo inválido",
"paymentLink.addressLine1": "Endereço",
Expand Down
Loading
Loading