From c42e0546253bf50dffb7d217f8fe2995ba136c29 Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Mon, 26 Aug 2024 20:34:42 +0530 Subject: [PATCH 1/2] added background listner for the response --- payment_sdk/src/components/ResponseScreen.tsx | 1 + payment_sdk/src/components/SubmitButton.tsx | 13 +++- payment_sdk/src/context/MainStateProvider.tsx | 64 ++++++++++++++++++- payment_sdk/src/context/state.ts | 6 ++ payment_sdk/src/util/types.ts | 5 ++ 5 files changed, 84 insertions(+), 5 deletions(-) diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index f9a8858..ff2ad99 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -111,6 +111,7 @@ const getStyles = (theme: ThemeSchemeType) => { marginBottom: responsiveScale(16), textAlign: "center", paddingHorizontal: responsiveScale(32), + color: theme.TEXT_COLOR, }, bottomButton: { position: "absolute", diff --git a/payment_sdk/src/components/SubmitButton.tsx b/payment_sdk/src/components/SubmitButton.tsx index 7b73bb5..2ce949b 100644 --- a/payment_sdk/src/components/SubmitButton.tsx +++ b/payment_sdk/src/components/SubmitButton.tsx @@ -1,9 +1,11 @@ -import React from "react"; +import React, { useContext } 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"; @@ -20,11 +22,18 @@ 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)} diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index f7b6dac..76f77da 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, Linking } from "react-native"; +import { Alert, AppState, Linking } from "react-native"; import i18next from "i18next"; @@ -30,11 +30,12 @@ import { import { validateSessionResponse } from "@util/validator"; import "@assets/languages/i18n"; -import { Actions, DispatchContext, KomojuContext } from "./state"; +import { Actions, DispatchContext, KomojuContext, StateContext } 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 @@ -56,6 +57,62 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { }; }, [props]); + useEffect(() => { + // Add event listener for deep links + const subscription = AppState.addEventListener( + "change", + handleBackgroundStateChange + ); + + return () => { + subscription.remove(); + }; + }, [processedPayment]); + + // This callback method is used to check the background state + const handleBackgroundStateChange = async () => { + startLoading(); + + if (processedPayment) { + // if this is a session flow, check until session response changes from 'pending' to 'completed' or 'error' + const sessionShowPayload = { + publishableKey: props.publishableKey, + sessionId: sessionIdRef.current, + }; + + // 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 = 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 + 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) { + onPaymentCancelled(); + } else { + onPaymentFailed(); + } + } + dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: false }); + // after all api calls are done stopping the loading indicator + stopLoading(); + }; + const openPaymentSheet = () => { if (props?.useBottomSheet) { sheetRef?.current?.open(); @@ -233,6 +290,7 @@ 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(); }; @@ -302,7 +360,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 aea5ef8..6e78765 100644 --- a/payment_sdk/src/context/state.ts +++ b/payment_sdk/src/context/state.ts @@ -32,6 +32,7 @@ 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", }; /** @@ -123,6 +124,11 @@ 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/util/types.ts b/payment_sdk/src/util/types.ts index 0d454c3..8943485 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -219,6 +219,10 @@ 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. */ @@ -262,6 +266,7 @@ export type sessionPayProps = { export const initialState: State = { paymentType: PaymentType.CREDIT, loading: false, + processedPayment: false, /** credit card payment related states start */ cardholderName: "", From 626827547e220bd28f87ce8573b37c4eb0495d7d Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Mon, 26 Aug 2024 21:54:35 +0530 Subject: [PATCH 2/2] added background listner for the response --- payment_sdk/src/assets/languages/en.json | 2 ++ payment_sdk/src/assets/languages/ja.json | 4 +++- payment_sdk/src/components/PaymentModal.tsx | 6 +++++- payment_sdk/src/components/ResponseScreen.tsx | 5 +++++ payment_sdk/src/context/MainStateProvider.tsx | 12 +++++++++++- payment_sdk/src/util/types.ts | 4 ++++ 6 files changed, 30 insertions(+), 3 deletions(-) diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index 38c850e..08c0eec 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -45,6 +45,8 @@ "PAYMENT_CANCELLED_MSG": "We noticed that you’ve canceled the payment process. If this was a mistake, you can try again to complete your purchase.", "BACK_TO_STORE": "Back to store", "UPDATE_PAYMENT_METHOD": "Update payment method", + "SESSION_EXPIRED": "Session Expired", + "SESSION_EXPIRED_MSG": "This session has already expired. Please try again.", "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 3e13d62..98c0a41 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -44,7 +44,9 @@ "PAYMENT_RE_TRY_MSG": "入力していただいたカードで支払いを行うことができませんでした。別の支払い方法を試してみてください。", "PAYMENT_CANCELLED_MSG": "お支払いプロセスがキャンセルされたことを確認しました。もしこれが誤りであれば、再度お試しいただき、購入を完了してください。", "BACK_TO_STORE": "ストアに戻る", - "UPDATE_PAYMENT_METHOD": "支払い方法を変更する", + "UPDATE_PAYMENT_METHOD": "支払い方法を変更する", + "SESSION_EXPIRED": "セッションが期限切れ", + "SESSION_EXPIRED_MSG": "このセッションは既に期限切れです。再度お試しください。", "PAYMENT_VIA_ALI_PAY": "Alipayによる支払い", "ALI_PAY_REDIRECT_MESSAGE": "支払いを完了するには、Alipay にリダイレクトされます。", diff --git a/payment_sdk/src/components/PaymentModal.tsx b/payment_sdk/src/components/PaymentModal.tsx index b30995c..befbf28 100644 --- a/payment_sdk/src/components/PaymentModal.tsx +++ b/payment_sdk/src/components/PaymentModal.tsx @@ -81,6 +81,7 @@ const PaymentModal = ({ case ResponseScreenStatuses.SUCCESS: case ResponseScreenStatuses.COMPLETE: case ResponseScreenStatuses.CANCELLED: + case ResponseScreenStatuses.EXPIRED: return paymentSuccessCtaText; case ResponseScreenStatuses.FAILED: return paymentFailedCtaText; @@ -95,6 +96,8 @@ const PaymentModal = ({ case ResponseScreenStatuses.COMPLETE: case ResponseScreenStatuses.CANCELLED: return closeSheet(false); + case ResponseScreenStatuses.EXPIRED: + return closeSheet(false); case ResponseScreenStatuses.FAILED: return dispatch({ type: Actions.SET_PAYMENT_STATE, @@ -110,7 +113,8 @@ const PaymentModal = ({ !( paymentState === ResponseScreenStatuses.SUCCESS || paymentState === ResponseScreenStatuses.CANCELLED || - paymentState === ResponseScreenStatuses.COMPLETE + paymentState === ResponseScreenStatuses.COMPLETE || + paymentState === ResponseScreenStatuses.EXPIRED ) ); }; diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index ff2ad99..6b1cddb 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -38,6 +38,11 @@ const statusConfigs: Partial> = { defaultMessage: "PAYMENT_CANCELLED_MSG", image: require("../assets/images/awaitingPayment.png"), }, + [ResponseScreenStatuses.EXPIRED]: { + title: "SESSION_EXPIRED", + defaultMessage: "SESSION_EXPIRED_MSG", + image: require("../assets/images/error.png"), + }, }; type Props = { diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index 76f77da..480f28f 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -86,7 +86,8 @@ 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?.expired ) { sessionResponse = await sessionShow(sessionShowPayload); } @@ -104,6 +105,8 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { onCompleteCallback.current(sessionResponse); } else if (sessionResponse?.payment?.status === PaymentStatuses.CANCELLED) { onPaymentCancelled(); + } else if (sessionResponse?.expired) { + onSessionExpired() } else { onPaymentFailed(); } @@ -168,6 +171,13 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { }); }; + // when payment is failed invoking the error screen + const onSessionExpired = () => + dispatch({ + type: Actions.SET_PAYMENT_STATE, + payload: ResponseScreenStatuses.EXPIRED, + }); + const onUserCancel = async () => { if (onDismissCallback.current) { const sessionShowPayload = { diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index 8943485..88c3ea9 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -75,6 +75,7 @@ export enum PaymentStatuses { SUCCESS = "completed", PENDING = "pending", CANCELLED = "cancelled", + EXPIRED = "expired", } export enum TokenResponseStatuses { @@ -93,6 +94,8 @@ export enum ResponseScreenStatuses { COMPLETE = "complete", /** For displaying payment instruction screens for cancelled by the user */ CANCELLED = "cancelled", + /** For displaying payment instruction screens for expired user session */ + EXPIRED = "expired", } export enum CurrencySign { @@ -250,6 +253,7 @@ export type State = CardDetailsType & | ResponseScreenStatuses.COMPLETE | ResponseScreenStatuses.FAILED | ResponseScreenStatuses.CANCELLED + | ResponseScreenStatuses.EXPIRED | ""; /** * States of the Bank transfer and Pay Easy fields.