From 5b9d188fc0e841e90ec0f23b82800d1983b2e1f3 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Tue, 27 Aug 2024 22:39:10 +0530 Subject: [PATCH 1/3] Main state provider functionality update --- example/PaymentScreen.tsx | 4 +- payment_sdk/src/assets/languages/en.json | 2 + payment_sdk/src/assets/languages/ja.json | 6 +- payment_sdk/src/components/SubmitButton.tsx | 15 +--- payment_sdk/src/context/MainStateProvider.tsx | 73 +++++++++---------- payment_sdk/src/context/state.ts | 6 -- payment_sdk/src/index.ts | 10 ++- payment_sdk/src/util/constants.ts | 2 +- payment_sdk/src/util/types.ts | 5 -- 9 files changed, 56 insertions(+), 67 deletions(-) diff --git a/example/PaymentScreen.tsx b/example/PaymentScreen.tsx index 861a6cb..f6d411b 100644 --- a/example/PaymentScreen.tsx +++ b/example/PaymentScreen.tsx @@ -9,7 +9,7 @@ import { Pressable, Alert, } from 'react-native'; -import {KomojuSDK} from '@komoju/komoju-react-native'; +import {KomojuSDK, SessionShowResponseType} from '@komoju/komoju-react-native'; import createSession from './services/sessionService'; export enum CurrencyTypes { @@ -51,7 +51,7 @@ const PaymentScreen = ({ }; // when the payment is complete pass a callback to get the final results of response - const onPaymentComplete = (response: any) => { + const onPaymentComplete = (response: SessionShowResponseType) => { console.log(`Transaction Status: ${response?.status}`); setAmount(''); }; diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index 08c0eec..383cc92 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -47,6 +47,8 @@ "UPDATE_PAYMENT_METHOD": "Update payment method", "SESSION_EXPIRED": "Session Expired", "SESSION_EXPIRED_MSG": "This session has already expired. Please try again.", + "PAYMENT_WAITING": "Awaiting Payment", + "PAYMENT_WAITING_MSG": "Your payment has not been completed yet. We have e-mailed you the instructions on how to complete your purchase.", "PAYMENT_VIA_ALI_PAY": "Payment via Alipay", "ALI_PAY_REDIRECT_MESSAGE": "You will be redirected to Alipay to complete the payment", diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 98c0a41..d436d12 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -47,7 +47,9 @@ "UPDATE_PAYMENT_METHOD": "支払い方法を変更する", "SESSION_EXPIRED": "セッションが期限切れ", "SESSION_EXPIRED_MSG": "このセッションは既に期限切れです。再度お試しください。", - + "PAYMENT_WAITING": "支払い待ち", + "PAYMENT_WAITING_MSG": "お支払いはまだ完了していません。購入を完了するための手順を電子メールで送信しました。", + "PAYMENT_VIA_ALI_PAY": "Alipayによる支払い", "ALI_PAY_REDIRECT_MESSAGE": "支払いを完了するには、Alipay にリダイレクトされます。", "CONTINUE_TO_ALI_PAY": "アリペイに進む", @@ -86,4 +88,4 @@ "EMAIL_ERROR": "有効なメールアドレスを入力してください", "": "" } - } + } \ No newline at end of file diff --git a/payment_sdk/src/components/SubmitButton.tsx b/payment_sdk/src/components/SubmitButton.tsx index 2ce949b..ac32fac 100644 --- a/payment_sdk/src/components/SubmitButton.tsx +++ b/payment_sdk/src/components/SubmitButton.tsx @@ -1,11 +1,9 @@ -import React, { useContext } from "react"; +import React from "react"; import { StyleSheet, Text, TouchableOpacity } from "react-native"; import { useTranslation } from "react-i18next"; -import { Actions, DispatchContext } from "@context/state"; - import { ThemeSchemeType } from "@util/types"; import { resizeFonts, responsiveScale } from "@theme/scalling"; @@ -22,18 +20,12 @@ const SubmitButton = ({ label, labelSuffix, onPress, testID }: Props) => { const { t } = useTranslation(); const theme = useCurrentTheme(); const styles = getStyles(theme); - const dispatch = useContext(DispatchContext); - - const onSubmit = () => { - dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: true }); - onPress(); - } return ( {labelSuffix ? `${t(label)} ${labelSuffix}` : t(label)} @@ -61,5 +53,4 @@ const getStyles = (theme: ThemeSchemeType) => { fontWeight: "bold", }, }); - -} \ No newline at end of file +}; diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index 480f28f..df9e7cf 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -7,7 +7,7 @@ import React, { useState, } from "react"; -import { Alert, AppState, Linking } from "react-native"; +import { Alert, AppState, AppStateStatus, Linking } from "react-native"; import i18next from "i18next"; @@ -26,16 +26,16 @@ import { PaymentStatuses, ResponseScreenStatuses, sessionPayProps, + TokenResponseStatuses, } from "@util/types"; import { validateSessionResponse } from "@util/validator"; import "@assets/languages/i18n"; -import { Actions, DispatchContext, KomojuContext, StateContext } from "./state"; +import { Actions, DispatchContext, KomojuContext } from "./state"; export const MainStateProvider = (props: KomojuProviderIprops) => { const dispatch = useContext(DispatchContext); const [modalVisible, setModalVisible] = useState(false); - const { processedPayment } = useContext(StateContext) const sheetRef = useRef(null); // ref to hold client provided onComplete callback @@ -52,28 +52,23 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { handleDeepLinkStateChange ); - return () => { - subscription.remove(); - }; - }, [props]); - - useEffect(() => { // Add event listener for deep links - const subscription = AppState.addEventListener( + const windowChangeListener = AppState.addEventListener( "change", handleBackgroundStateChange ); return () => { subscription.remove(); + windowChangeListener.remove(); }; - }, [processedPayment]); + }, [props]); // This callback method is used to check the background state - const handleBackgroundStateChange = async () => { + const handleBackgroundStateChange = async (status: AppStateStatus) => { startLoading(); - if (processedPayment) { + if (status === "active") { // if this is a session flow, check until session response changes from 'pending' to 'completed' or 'error' const sessionShowPayload = { publishableKey: props.publishableKey, @@ -81,37 +76,37 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { }; // fetch session status to check if the payment is completed - let sessionResponse = await sessionShow(sessionShowPayload); - - // Polling until session verification status changes - while ( - sessionResponse?.status === PaymentStatuses.PENDING && - sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED && - !sessionResponse?.expired - ) { - sessionResponse = await sessionShow(sessionShowPayload); - } + const sessionResponse = await sessionShow(sessionShowPayload); // if payment success showing success screen or if failed showing error screen if (sessionResponse?.status === PaymentStatuses.SUCCESS) { - if (sessionResponse?.payment?.payment_details?.instructions_url) { - openURL(sessionResponse?.payment?.payment_details?.instructions_url); - } - onPaymentSuccess(); - // calling user passed onComplete method with session response data + if ( + sessionResponse?.payment?.status === TokenResponseStatuses.CAPTURED + ) { + onPaymentSuccess(); + } else { + onPaymentAwaiting(); + } // calling user passed onComplete method with session response data onCompleteCallback.current && // TODO: Fix this type error // @ts-expect-error - Argument of type 'PaymentSessionResponse' is not assignable to parameter of type 'string'. onCompleteCallback.current(sessionResponse); - } else if (sessionResponse?.payment?.status === PaymentStatuses.CANCELLED) { + } else if ( + sessionResponse?.payment?.status === PaymentStatuses.CANCELLED + ) { onPaymentCancelled(); } else if (sessionResponse?.expired) { - onSessionExpired() - } else { + onSessionExpired(); + } else if ( + sessionResponse?.status === PaymentStatuses.ERROR || + sessionResponse?.payment?.status === PaymentStatuses.ERROR || + sessionResponse?.secure_token?.verification_status === + TokenResponseStatuses.ERROR + ) { onPaymentFailed(); } } - dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: false }); + // after all api calls are done stopping the loading indicator stopLoading(); }; @@ -278,17 +273,20 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // Polling until session verification status changes while ( sessionResponse?.status === PaymentStatuses.PENDING && - sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED + sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED && + sessionResponse?.secure_token?.verification_status !== + TokenResponseStatuses.ERROR ) { sessionResponse = await sessionShow(sessionShowPayload); } // if payment success showing success screen or if failed showing error screen if (sessionResponse?.status === PaymentStatuses.SUCCESS) { - if (sessionResponse?.payment?.payment_details?.instructions_url) { - openURL(sessionResponse?.payment?.payment_details?.instructions_url); + if (sessionResponse?.payment?.status === TokenResponseStatuses.CAPTURED) { + onPaymentSuccess(); + } else { + onPaymentAwaiting(); } - onPaymentSuccess(); // calling user passed onComplete method with session response data onCompleteCallback.current && // TODO: Fix this type error @@ -300,7 +298,6 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { onPaymentFailed(); } - dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: false }); // after all api calls are done stopping the loading indicator stopLoading(); }; @@ -370,7 +367,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // TODO: Fix this type error // eslint-disable-next-line @typescript-eslint/no-unused-vars - const initializeKomoju = useCallback((params: InitPrams) => { }, []); + const initializeKomoju = useCallback((params: InitPrams) => {}, []); const renderPaymentUI = useMemo(() => { const UI = props?.useBottomSheet ? ( diff --git a/payment_sdk/src/context/state.ts b/payment_sdk/src/context/state.ts index 6e78765..aea5ef8 100644 --- a/payment_sdk/src/context/state.ts +++ b/payment_sdk/src/context/state.ts @@ -32,7 +32,6 @@ export const Actions = { SET_PAYMENT_STATE: "SET_PAYMENT_STATE", SET_PAYMENT_METHODS: "SET_PAYMENT_METHODS", SESSION_PAY: "SESSION_PAY", - SET_PROCEED_PAYMENT: "SET_PROCEED_PAYMENT", }; /** @@ -124,11 +123,6 @@ export function reducer(state: State, action: ActionType) { ...state, paymentMethods: action.payload, }; - case Actions.SET_PROCEED_PAYMENT: - return { - ...state, - processedPayment: action.payload, - }; default: throw new Error(); } diff --git a/payment_sdk/src/index.ts b/payment_sdk/src/index.ts index 59a8d7f..e1ae8a3 100644 --- a/payment_sdk/src/index.ts +++ b/payment_sdk/src/index.ts @@ -3,7 +3,11 @@ import { useContext } from "react"; import { KomojuProvider } from "@context/KomojuProvider"; import { KomojuContext } from "@context/state"; -import { LanguageTypes, PaymentType } from "@util/types"; +import { + LanguageTypes, + PaymentType, + SessionShowResponseType, +} from "@util/types"; /** * KomojuSDK provides context utilities for the Komoju payment system. @@ -33,4 +37,8 @@ export { * Supported languages to parse for language prop. */ LanguageTypes, + /** + * Response payload for onComplete and onDismiss callbacks. + */ + SessionShowResponseType, }; diff --git a/payment_sdk/src/util/constants.ts b/payment_sdk/src/util/constants.ts index 52ef1d2..ff5a8d7 100644 --- a/payment_sdk/src/util/constants.ts +++ b/payment_sdk/src/util/constants.ts @@ -14,7 +14,7 @@ export const API_HEADER = (publishableKey: string) => ({ export const paymentSuccessCtaText = "BACK_TO_STORE"; export const paymentFailedCtaText = "UPDATE_PAYMENT_METHOD"; -export const emailRegex = /^[a-zA-Z0–9._-]+@[a-zA-Z0–9.-]+\.[a-zA-Z]{2,4}$/; +export const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; export const cardTypeRegex = { amex: /^3[47]\d{0,13}/, diner: /^3(?:0([0-5]|9)|[689]\d?)\d{0,11}/, diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index 88c3ea9..c00a29b 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -222,10 +222,6 @@ export type State = CardDetailsType & * Global loading state. to display loading animation over sdk and disable buttons. */ loading: boolean; - /** - * Global processedPayment state. to indicate whether the payment was processed - */ - processedPayment: boolean; /** * Callback function to call relevant api for each payment type. */ @@ -270,7 +266,6 @@ export type sessionPayProps = { export const initialState: State = { paymentType: PaymentType.CREDIT, loading: false, - processedPayment: false, /** credit card payment related states start */ cardholderName: "", From 541ecb4573d1aedd3bcfbad93be5f3c9e6e68f7b Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Tue, 27 Aug 2024 23:18:19 +0530 Subject: [PATCH 2/3] Loader zIndex and keyboard dismiss handled --- payment_sdk/src/components/Loader.tsx | 1 + payment_sdk/src/components/SheetContent.tsx | 1 + payment_sdk/src/components/SubmitButton.tsx | 9 +++++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/payment_sdk/src/components/Loader.tsx b/payment_sdk/src/components/Loader.tsx index b1e4e08..9889bea 100644 --- a/payment_sdk/src/components/Loader.tsx +++ b/payment_sdk/src/components/Loader.tsx @@ -17,5 +17,6 @@ const styles = StyleSheet.create({ ...StyleSheet.absoluteFillObject, alignItems: "center", justifyContent: "center", + zIndex: 6, }, }); diff --git a/payment_sdk/src/components/SheetContent.tsx b/payment_sdk/src/components/SheetContent.tsx index 0cb49c7..54af7d9 100644 --- a/payment_sdk/src/components/SheetContent.tsx +++ b/payment_sdk/src/components/SheetContent.tsx @@ -102,6 +102,7 @@ const SheetContent = () => { } ListFooterComponentStyle={styles.footerContent} + keyboardShouldPersistTaps="handled" /> ); diff --git a/payment_sdk/src/components/SubmitButton.tsx b/payment_sdk/src/components/SubmitButton.tsx index ac32fac..eee11e5 100644 --- a/payment_sdk/src/components/SubmitButton.tsx +++ b/payment_sdk/src/components/SubmitButton.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { StyleSheet, Text, TouchableOpacity } from "react-native"; +import { Keyboard, StyleSheet, Text, TouchableOpacity } from "react-native"; import { useTranslation } from "react-i18next"; @@ -21,11 +21,16 @@ const SubmitButton = ({ label, labelSuffix, onPress, testID }: Props) => { const theme = useCurrentTheme(); const styles = getStyles(theme); + const onSubmit = () => { + Keyboard.dismiss(); + onPress(); + }; + return ( {labelSuffix ? `${t(label)} ${labelSuffix}` : t(label)} From d9625135657be665ca4ceda69679775f982c2e34 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Wed, 28 Aug 2024 09:11:50 +0530 Subject: [PATCH 3/3] Fixing card payment without 3Ds --- payment_sdk/src/context/MainStateProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index df9e7cf..3f60051 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -321,7 +321,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { if (response?.status === PaymentStatuses.PENDING) { openURL(response.redirect_url); } else if (response?.status === PaymentStatuses.SUCCESS) { - if (response?.payment?.status === PaymentStatuses.SUCCESS) { + if (response?.payment?.status === TokenResponseStatuses.CAPTURED) { onPaymentSuccess(); } else if (response?.payment?.payment_details?.instructions_url) { openURL(response?.payment?.payment_details?.instructions_url);