Skip to content

Commit

Permalink
Merge pull request #80 from degica/task/MOB-63
Browse files Browse the repository at this point in the history
Support Customer Mode Tokenization
  • Loading branch information
tharindu-rhino authored Sep 19, 2024
2 parents dc9ae7e + b83ae1e commit d395b72
Show file tree
Hide file tree
Showing 20 changed files with 334 additions and 207 deletions.
44 changes: 26 additions & 18 deletions src/__tests__/CardInputGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import React, { ReactNode } from "react";
import { render, fireEvent } from "@testing-library/react-native";

import { determineCardType, formatCreditCardNumber, formatExpiry } from "../util/helpers";
import {
determineCardType,
formatCreditCardNumber,
formatExpiry,
} from "../util/helpers";
import { Actions, DispatchContext, StateContext } from "../context/state";
import CardInputGroup from "../components/CardInputGroup";
import { isCardNumberValid, validateCardExpiry } from "../util/validator";
Expand All @@ -25,9 +29,11 @@ jest.mock("../util/validator", () => ({

// Mocked context values
const mockState = {
cardCVV: "",
cardNumber: "",
cardExpiredDate: "",
cardData: {
cardCVV: "",
cardNumber: "",
cardExpiredDate: "",
},
};

const inputGroupMockValues = {
Expand Down Expand Up @@ -60,18 +66,20 @@ describe("CardInputGroup Component", () => {
const { getByTestId } = renderWithContext(
<CardInputGroup
inputErrors={inputGroupMockValues}
resetError={(data: string) => { }}
resetError={(data: string) => {}}
/>
);

// Check if the inputs render with the initial values from context
expect(getByTestId("cardNumberInput").props.value).toBe(
mockState.cardNumber
mockState.cardData.cardNumber
);
expect(getByTestId("cardExpiryInput").props.value).toBe(
mockState.cardExpiredDate
mockState.cardData.cardExpiredDate
);
expect(getByTestId("cardCVVInput").props.value).toBe(
mockState.cardData.cardCVV
);
expect(getByTestId("cardCVVInput").props.value).toBe(mockState.cardCVV);
});

it("updates card number when valid and dispatches action", () => {
Expand All @@ -82,7 +90,7 @@ describe("CardInputGroup Component", () => {
const { getByTestId } = renderWithContext(
<CardInputGroup
inputErrors={inputGroupMockValues}
resetError={(data: string) => { }}
resetError={(data: string) => {}}
/>
);

Expand All @@ -96,8 +104,8 @@ describe("CardInputGroup Component", () => {

// Check if the dispatch was called with the correct action
expect(mockDispatch).toHaveBeenCalledWith({
type: Actions.SET_CARD_NUMBER,
payload: "1234 1234 1234 1234",
type: Actions.SET_CARD_DATA,
payload: { cardNumber: "1234 1234 1234 1234" },
});
});

Expand All @@ -107,7 +115,7 @@ describe("CardInputGroup Component", () => {
const { getByTestId } = renderWithContext(
<CardInputGroup
inputErrors={inputGroupMockValues}
resetError={(data: string) => { }}
resetError={(data: string) => {}}
/>
);

Expand All @@ -130,7 +138,7 @@ describe("CardInputGroup Component", () => {
const { getByTestId } = renderWithContext(
<CardInputGroup
inputErrors={inputGroupMockValues}
resetError={(data: string) => { }}
resetError={(data: string) => {}}
/>
);

Expand All @@ -144,16 +152,16 @@ describe("CardInputGroup Component", () => {

// Check if the dispatch was called with the correct action
expect(mockDispatch).toHaveBeenCalledWith({
type: Actions.SET_CARD_EXPIRED_DATE,
payload: "12 / 25",
type: Actions.SET_CARD_DATA,
payload: { cardExpiredDate: "12 / 25" },
});
});

it("updates card CVV and dispatches action", () => {
const { getByTestId } = renderWithContext(
<CardInputGroup
inputErrors={inputGroupMockValues}
resetError={(data: string) => { }}
resetError={(data: string) => {}}
/>
);

Expand All @@ -164,8 +172,8 @@ describe("CardInputGroup Component", () => {

// Check if the dispatch was called with the correct action
expect(mockDispatch).toHaveBeenCalledWith({
type: Actions.SET_CARD_CVV,
payload: "123",
type: Actions.SET_CARD_DATA,
payload: { cardCVV: "123" },
});
});
});
1 change: 1 addition & 0 deletions src/assets/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"CARD_NUMBER": "Card Number",
"SCAN_CARD": "Scan Card",
"PAY": "Pay",
"SAVE": "Save",
"NAME_SHOWN_ON_RECEIPT": "Name (shown on receipt)",
"FULL_NAME_ON_RECEIPT": "Full name on receipt",
"EMAIL": "Email",
Expand Down
1 change: 1 addition & 0 deletions src/assets/languages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"CARD_NUMBER": "カード番号",
"SCAN_CARD": "カードをスキャン",
"PAY": "支払い",
"SAVE": "保存",
"NAME_SHOWN_ON_RECEIPT": "氏名",
"FULL_NAME_ON_RECEIPT": "氏名を入力(レシートで表示されます)",
"EMAIL": "メールアドレス",
Expand Down
46 changes: 24 additions & 22 deletions src/components/CardInputGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
memo,
useContext,
useEffect,
useState,
useCallback,
} from "react";
import { memo, useContext, useEffect, useState, useCallback } from "react";

import { StyleSheet, View, Image, Dimensions } from "react-native";

Expand All @@ -18,6 +12,7 @@ import {
import {
CardTypes,
PaymentType,
sessionDataType,
sessionShowPaymentMethodType,
ThemeSchemeType,
} from "../util/types";
Expand Down Expand Up @@ -50,16 +45,16 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => {
// const [toggleScanCard, setToggleScanCard] = useState<boolean>(false);
const theme = useCurrentTheme();
const styles = getStyles(theme);
const { cardCVV, cardNumber, cardExpiredDate, paymentMethods } =
useContext(StateContext);
const { cardData, sessionData } = useContext(StateContext);
const SessionData = sessionData as sessionDataType;

useEffect(() => {
// Determine card type and set it on first render if cardNumber is not empty
if (cardNumber) {
const type = determineCardType(cardNumber);
if (cardData?.cardNumber) {
const type = determineCardType(cardData?.cardNumber);
setCardType(type);
}
}, [cardNumber]);
}, [cardData?.cardNumber]);

//Toggle card scanner
// const toggleCardScanner = () => {
Expand All @@ -83,7 +78,7 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => {
// Create card image list
const cardImage = useCallback(() => {
// Select credit card payment method data from session response payment methods
const cardPaymentMethodData = paymentMethods?.find(
const cardPaymentMethodData = SessionData?.paymentMethods?.find(
(method: sessionShowPaymentMethodType) =>
method?.type === PaymentType.CREDIT
);
Expand Down Expand Up @@ -116,7 +111,7 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => {
} else {
return;
}
}, [cardType, paymentMethods]);
}, [cardType, SessionData?.paymentMethods]);

// const onCardScanned = useCallback(
// (cardDetails: { cardNumber?: string; expirationDate?: string }) => {
Expand Down Expand Up @@ -163,7 +158,7 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => {
]}
>
<Input
value={cardNumber ?? ""}
value={cardData?.cardNumber ?? ""}
testID="cardNumberInput"
keyboardType="number-pad"
placeholder="1234 1234 1234 1234"
Expand All @@ -172,8 +167,10 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => {
if (isCardNumberValid(text)) {
const derivedText = formatCreditCardNumber(text);
dispatch({
type: Actions.SET_CARD_NUMBER,
payload: derivedText,
type: Actions.SET_CARD_DATA,
payload: {
cardNumber: derivedText,
},
});
// Determine card type and set it
const type = determineCardType(text);
Expand All @@ -193,16 +190,18 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => {
]}
>
<Input
value={cardExpiredDate as string}
value={cardData?.cardExpiredDate as string}
keyboardType="number-pad"
testID="cardExpiryInput"
placeholder="MM / YY"
onChangeText={(text: string) => {
resetError("expiry");
if (validateCardExpiry(text)) {
dispatch({
type: Actions.SET_CARD_EXPIRED_DATE,
payload: formatExpiry(text),
type: Actions.SET_CARD_DATA,
payload: {
cardExpiredDate: formatExpiry(text),
},
});
}
}}
Expand All @@ -214,15 +213,18 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => {
style={[styles.itemRow, inputErrors.cvv && styles.errorContainer]}
>
<Input
value={cardCVV as string}
value={cardData?.cardCVV as string}
testID="cardCVVInput"
keyboardType="number-pad"
placeholder="CVV"
onChangeText={(text: string) => {
resetError("cvv");

if (text?.length < 11)
dispatch({ type: Actions.SET_CARD_CVV, payload: text });
dispatch({
type: Actions.SET_CARD_DATA,
payload: { cardCVV: text },
});
}}
inputStyle={[
styles.cvvInputStyle,
Expand Down
12 changes: 9 additions & 3 deletions src/components/PillContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { StyleSheet, View, Image, FlatList } from "react-native";

import { StateContext } from "../context/state";

import { PaymentType, sessionShowPaymentMethodType } from "../util/types";
import {
PaymentType,
sessionDataType,
sessionShowPaymentMethodType,
} from "../util/types";

import PaymentMethodImages from "../assets/images/paymentMethodImages";

Expand All @@ -24,7 +28,9 @@ const squareSizeImages = [
];

const PillContainer = ({ onSelect, selectedItem }: Props) => {
const { paymentMethods } = useContext(StateContext);
const { sessionData } = useContext(StateContext) as {
sessionData: sessionDataType;
};

const getIcon = (slug: PaymentType) => {
return (
Expand Down Expand Up @@ -53,7 +59,7 @@ const PillContainer = ({ onSelect, selectedItem }: Props) => {
return (
<View style={styles.container}>
<FlatList
data={paymentMethods}
data={sessionData.paymentMethods}
renderItem={renderItem}
horizontal
showsHorizontalScrollIndicator={false}
Expand Down
Loading

0 comments on commit d395b72

Please sign in to comment.