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

cancellation flow integration #17

Merged
merged 2 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion example/PaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,23 @@ const PaymentScreen = () => {
createPayment({
sessionId,
onComplete: onPaymentComplete,
onDismiss: onPaymentComplete,
});
};

const handleSecureTokenPayment = async () => {
createPayment({
sessionId: '',
onComplete: onPaymentComplete,
onDismiss: onPaymentComplete,
secretKey: SECRET_KEY,
enablePayWithoutSession: true,
});
};

// when the payment is complete pass a callback to get the final results of response
const onPaymentComplete = (response: any) => {
console.log(response);
console.log(`Transaction Status: ${response?.status}`);
setAmount('');
};

Expand Down
77 changes: 65 additions & 12 deletions payment_sdk/src/KomojuProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
TokenResponseStatuses,
webViewDataInitialState,
} from "./util/types";
import { PaymentSheetRefProps } from "./components/PaymentSheet";

import { Actions, DispatchContext, KomojuContext } from "./state";
import StateProvider from "./components/paymentState/stateProvider";
Expand All @@ -29,9 +28,9 @@ import { validateSessionResponse } from "./util/validator";
import secureTokenService, {
checkSecureTokenStatus,
} from "./services/secureTokenService";
import { parameterName } from "./util/constants";
import { sessionParameterName, tokenParameterName } from "./util/constants";
import paymentService from "./services/paymentService";
import Sheet from "./components/Sheet";
import Sheet, { SheetRefProps } from "./components/Sheet";

type KomojuProviderIprops = {
children?: ReactNode | ReactNode[];
Expand All @@ -53,7 +52,11 @@ export const KomojuProvider = (props: KomojuProviderIprops) => {
export const MainStateProvider = (props: KomojuProviderIprops) => {
const dispatch = useContext(DispatchContext);

const sheetRef = useRef<PaymentSheetRefProps>(null);
const sheetRef = useRef<SheetRefProps>(null);
// ref to hold client provided onDismiss callback
const onDismissCallback = useRef(null);
// ref to hold client provided session Id
const sessionIdRef = useRef("");

// when payment is success global state is rest and invoking the success screen
const onPaymentSuccess = () => {
Expand All @@ -74,6 +77,20 @@ export const MainStateProvider = (props: KomojuProviderIprops) => {
payload: ResponseScreenStatuses.FAILED,
});

const onUserCancel = async () => {
if (onDismissCallback.current) {
const sessionShowPayload = {
publicKey: props?.publicKey,
sessionId: sessionIdRef.current,
};

// fetch session status to check if the payment is completed
let sessionResponse = await sessionShow(sessionShowPayload);
// invoking client provided onDismiss callback
onDismissCallback.current(sessionResponse);
}
};

// showing overlay loading indicator disabling all interactions
const startLoading = () =>
dispatch({
Expand All @@ -100,7 +117,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => {

// validating the session data and closing the payment gateway if data is not valid
if (validateSessionResponse(sessionData)) {
sheetRef?.current?.close();
sheetRef?.current?.close(false);
Alert.alert("Error", "Session expired");
} else {
// if session is valid setting amount, currency type at global store for future use
Expand All @@ -125,8 +142,11 @@ export const MainStateProvider = (props: KomojuProviderIprops) => {
const { url } = newNavState;
if (!url) return;

// Check if URL includes secure_token_id=
if (url.includes(parameterName)) {
// Check if its credit card method URL includes secure_token_id=
if (
url.includes(tokenParameterName) &&
paymentType === PaymentType.CREDIT
) {
// CLose web view and start loading
dispatch({
type: Actions.SET_WEBVIEW_LINK,
Expand Down Expand Up @@ -159,18 +179,17 @@ export const MainStateProvider = (props: KomojuProviderIprops) => {
TokenResponseStatuses.SUCCESS
) {
onPaymentSuccess();
// calling user passed onComplete method with session response data
onComplete && onComplete(sessionResponse);
} else {
onPaymentFailed();
}

// calling user passed onComplete method with session response data
onComplete && onComplete(sessionResponse);
} else {
//This is for manual 3D secure token handling flow

// getting secure_token_id parameter from the web view URL
const token = url.substring(
url.indexOf(parameterName) + parameterName.length
url.indexOf(tokenParameterName) + tokenParameterName.length
);

// checking token status if the 3D secure has passed
Expand Down Expand Up @@ -207,6 +226,34 @@ export const MainStateProvider = (props: KomojuProviderIprops) => {

// after all api calls are done stopping the loading indicator
stopLoading();

// if paypay payment method web view redirection flow
} else if (
url.includes(sessionParameterName) &&
paymentType === PaymentType.PAY_PAY
) {
// if this is a session flow, check until session response changes from 'pending' to 'completed' or 'error'
const sessionShowPayload = {
publicKey: props.publicKey,
sessionId: sessionId,
};

// 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 = await sessionShow(sessionShowPayload);
}

// if payment success showing success screen or if failed showing error screen
if (sessionResponse?.status === PaymentStatuses.SUCCESS) {
onPaymentSuccess();
// calling user passed onComplete method with session response data
onComplete && onComplete(sessionResponse);
} else {
onPaymentFailed();
}
}
};

Expand Down Expand Up @@ -271,6 +318,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => {
({
sessionId,
onComplete,
onDismiss,
secretKey,
enablePayWithoutSession,
}: CreatePaymentFuncType) => {
Expand All @@ -279,6 +327,11 @@ export const MainStateProvider = (props: KomojuProviderIprops) => {
payload: initialState,
});

// setting client provided onDismiss callback into a ref
onDismissCallback.current = onDismiss;
// setting client provided session Id and into a ref
sessionIdRef.current = sessionId;

if (!enablePayWithoutSession) validateSession(sessionId);

dispatch({
Expand All @@ -302,7 +355,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => {
const initializeKomoju = useCallback((params: InitPrams) => {}, []);

const renderPaymentUI = useMemo(() => {
const UI = <Sheet ref={sheetRef} />;
const UI = <Sheet ref={sheetRef} onDismiss={onUserCancel} />;
return UI;
}, [sheetRef]);

Expand Down
34 changes: 0 additions & 34 deletions payment_sdk/src/components/PaymentSheet.tsx

This file was deleted.

46 changes: 36 additions & 10 deletions payment_sdk/src/components/Sheet.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, {
useCallback,
useImperativeHandle,
forwardRef,
useContext,
useRef,
useEffect,
ForwardRefRenderFunction,
} from "react";
import {
Alert,
Dimensions,
Image,
StyleSheet,
Expand All @@ -32,6 +32,7 @@ const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 50;
type SheetProps = {
children?: React.ReactNode;
swipeClose?: boolean;
onDismiss?: () => void;
};

export type SheetRefProps = {
Expand All @@ -41,7 +42,7 @@ export type SheetRefProps = {
};

const Sheet: ForwardRefRenderFunction<SheetRefProps, SheetProps> = (
{ children, swipeClose },
{ swipeClose, onDismiss },
ref
) => {
const translateY = useRef(new RNAnimated.Value(0)).current;
Expand Down Expand Up @@ -86,15 +87,38 @@ const Sheet: ForwardRefRenderFunction<SheetRefProps, SheetProps> = (
return activeState.current;
}, []);

const closeSheet = (showAlert = true) => {
if (showAlert) {
// showing an alert when user try to close the SDK modal
Alert.alert("Cancel Payment?", "", [
{
text: "No",
onPress: () => scrollTo(MAX_TRANSLATE_Y + 50),
style: "cancel",
},
{
text: "Yes",
onPress: () => {
// invoking client provided onDismiss() callback when closing the SDK modal
onDismiss && onDismiss();
scrollTo(0);
},
},
]);
} else {
// invoking client provided callback when closing the SDK modal
onDismiss && onDismiss();
scrollTo(0);
}
};

useImperativeHandle(
ref,
() => ({
open: () => {
scrollTo(MAX_TRANSLATE_Y + 50);
},
close: () => {
scrollTo(0);
},
close: closeSheet,
scrollTo,
isActive,
}),
Expand All @@ -113,7 +137,7 @@ const Sheet: ForwardRefRenderFunction<SheetRefProps, SheetProps> = (
},
onPanResponderRelease: () => {
if (translateYState.current > -SCREEN_HEIGHT / 1.5) {
scrollTo(0);
closeSheet(false);
} else if (translateYState.current < -SCREEN_HEIGHT / 1.5) {
scrollTo(MAX_TRANSLATE_Y + 50);
}
Expand All @@ -135,7 +159,7 @@ const Sheet: ForwardRefRenderFunction<SheetRefProps, SheetProps> = (
const ctaOnPress = () => {
switch (paymentState) {
case ResponseScreenStatuses.SUCCESS:
return scrollTo(0);
return closeSheet(false);
case ResponseScreenStatuses.FAILED:
return dispatch({
type: Actions.SET_PAYMENT_STATE,
Expand All @@ -150,7 +174,7 @@ const Sheet: ForwardRefRenderFunction<SheetRefProps, SheetProps> = (
<>
<RNAnimated.View
onTouchStart={() => {
if (swipeClose) scrollTo(0);
if (swipeClose) closeSheet(false);
}}
pointerEvents="none"
style={[styles.backDrop, { opacity: active }]}
Expand All @@ -167,9 +191,11 @@ const Sheet: ForwardRefRenderFunction<SheetRefProps, SheetProps> = (
<Text style={styles.headerLabel}>Payment Options</Text>
<TouchableOpacity
style={styles.crossBtn}
onPress={() => scrollTo(0)}
onPress={() =>
closeSheet(paymentState !== ResponseScreenStatuses.SUCCESS)
}
>
<Image source={require("../assets/images/close.png")} />
<Image source={closeIcon} />
</TouchableOpacity>
</View>
</View>
Expand Down
5 changes: 4 additions & 1 deletion payment_sdk/src/util/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
export const noop = () => {};
export const BASE_URL = "https://komoju.com/api/v1";

export const parameterName = "secure_token_id=";
// redirect url parameter to identify if token validation is completed
export const tokenParameterName = "secure_token_id=";
// redirect url parameter to identify if session payment is completed
export const sessionParameterName = "sessions";

export const paymentSuccessCtaText = "Back to store";
export const paymentFailedCtaText = "Update payment method";
1 change: 1 addition & 0 deletions payment_sdk/src/util/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type CreatePaymentFuncType = {
sessionId: string;
secretKey?: string;
onComplete?: (response: any) => void; // callback when transaction is complete status is passed as a prop
onDismiss?: (response: any) => void; // callback when user closes the SDK modal
enablePayWithoutSession?: boolean;
};

Expand Down
Loading