gapp-payment-method-flow
Version:
Mobile Gapp flow for Payment Method
571 lines (505 loc) • 18.5 kB
text/typescript
import { useCallback, useEffect, useState } from 'react';
import { httpRequest } from '../shared/httpRequest';
import type { TDataLoadType, IModel, TValueOut } from './module.type';
import axios from 'axios';
/** Initial value for payment method */
const paymentMethodInitialValues = {
id: 0,
text: '',
value: '',
};
/** view model of payment method gapp */
function useViewModel({ dataLoad, dataIn, dataOut }: IModel) {
const [paymentMethods, setPaymentMethods] = useState([]);
const [newDataLoad, setNewDataLoad] = useState([]);
const [tempSelectedPaymentMethod, setTempSelectedPaymentMethod] = useState<
TValueOut['paymentMethod']
>(paymentMethodInitialValues);
const [paymentMethodDataOut, setPaymentMethodDataOut] = useState<TValueOut>({
loading: false,
paymentMethod: {},
cardDetails: {},
errorResponse: {},
savedCard: {},
defaultCardId: 0,
savedCards: dataLoad?.savedCards || [],
});
const {
data: dataLoadData,
axios: axiosInstance,
cardValidationToken,
endpoints,
token,
baseUrl,
validateCardUrl,
...resDataLoad
} = dataLoad || {};
const dataLoadType: TDataLoadType = dataIn.dataLoadType || 'json-stab';
const data =
dataLoadType === 'json-stab' ? dataLoadData : [...paymentMethods];
const isCardValidate: boolean = dataIn?.isCardValidate || false;
const cardFormTrigger: string = dataIn?.cardFormTrigger || '';
const cardFormTriggerKey: string = dataIn?.cardFormTriggerKey || 'text';
/** dataIn function to reconstruct the list of payment method */
const constructedData = () => {
const newData = data.map((i: any) => ({
id: i?.id,
text: i?.name,
value: i?.name,
subText: i.description,
descriptionText: '',
isExpanded: i.expanded || true,
child:
(i.merchant_processors || []).length > 0
? i.merchant_processors?.map((d: any) => ({
id: d?.id,
text: d.processors?.name,
value: d.processors?.name,
subText: d.processors?.description,
descriptionText: '',
imageUri: d.processors?.icon_url || undefined,
}))
: undefined,
}));
const isNewDataLoad = Array.isArray(newDataLoad) && newDataLoad.length > 0;
const dLoad = isNewDataLoad ? newDataLoad : newData;
if (dataIn.constructData) {
return dataIn.constructData(dLoad, data);
} else {
return dLoad || [];
}
};
/** dataOut function of Payment Method MiniApp
* like MethodSelection, MethodSelectionAccordion
*/
const handleSelectPaymentMethod = (values: any) => {
const newPaymentMethod: TValueOut['paymentMethod'] =
values?.type === 'Selected' && values?.value ? values?.value : values;
const newPaymentMethods =
values?.type === 'NewDataLoad' && values?.value
? values?.value
: newDataLoad;
setPaymentMethodDataOut({
...paymentMethodDataOut,
errorResponse: {},
paymentMethod: newPaymentMethod,
});
setNewDataLoad(newPaymentMethods);
};
/** dataOut function of Payment Method MiniApp
* (MethodSelectionAccordionScreen)
*/
const handleSubmitPaymentMethodAccordion = async (values: any) => {
const newPaymentMethod: TValueOut['paymentMethod'] = values?.selectedMethod;
const newCardDetails: TValueOut['cardDetails'] = {
...values.selectedSavedCard?.value,
isSavedCard: true,
};
const newSavedCard: TValueOut['savedCard'] = values?.selectedSavedCard;
const newPaymentMethods = values?.newData || newDataLoad;
const hasTokenId =
cardFormTrigger === newPaymentMethod[cardFormTriggerKey] &&
newSavedCard?.id &&
paymentMethodDataOut.cardDetails?.paymentTokenId
? true
: false;
const validatedCard: any = hasTokenId
? {}
: values?.selectedSavedCard?.value?.cardNumber &&
(await validateCreditDebitCard(values?.selectedSavedCard?.value));
setPaymentMethodDataOut({
...paymentMethodDataOut,
errorResponse: {},
cardDetails: Object.assign(newCardDetails, validatedCard),
paymentMethod: newPaymentMethod,
savedCard: newSavedCard,
});
setNewDataLoad(newPaymentMethods);
};
const handleCardFormTrigger = (methodValues: any) => {
setTempSelectedPaymentMethod(methodValues);
if (
dataIn?.actions?.handleCardFormTrigger &&
methodValues[cardFormTriggerKey] === cardFormTrigger
) {
dataIn?.actions?.handleCardFormTrigger(methodValues);
}
};
const handleAddNewCard = (methodValues: any) => {
setTempSelectedPaymentMethod(methodValues);
if (dataIn?.actions?.handleAddNewCard) {
dataIn?.actions?.handleAddNewCard(methodValues);
}
};
const validateCreditDebitCard = async (values: any) => {
try {
let validatedCard: any = {};
const cardPayload = {
card: {
number: values?.cardNumber?.replace(/-/g, ''),
expMonth: values.mm,
expYear: values.yy,
cvc: values.cvv,
},
};
setPaymentMethodDataOut({
...paymentMethodDataOut,
loading: true,
});
const networkServiceCondition1 =
dataLoadType === 'network-service-config' &&
validateCardUrl &&
cardValidationToken;
const networkServiceCondition2 =
dataLoadType === 'network-service' &&
cardValidationToken &&
validateCardUrl;
if (isCardValidate) {
if (networkServiceCondition1) {
const validateRes = await httpRequest({
tokenType: 'Basic',
...resDataLoad,
url: `${validateCardUrl}`,
method: 'POST',
bearerToken: cardValidationToken,
requestPostData: cardPayload,
});
const res = validateRes?.data || validateRes;
validatedCard = {
paymentTokenId: res?.paymentTokenId,
state: res?.state,
};
} else if (networkServiceCondition2) {
const validateRes: any = await axios.post(
validateCardUrl,
cardPayload,
{
headers: {
'Accept': 'application/json',
'Content-type': 'application/json;charset=utf-8',
'Authorization': `${
resDataLoad?.tokenType || 'Basic'
} ${cardValidationToken}`,
},
}
);
const res = validateRes?.data || validateRes;
validatedCard = {
paymentTokenId: res?.paymentTokenId,
state: res?.state,
};
}
}
return validatedCard;
} catch (err: any) {
const error = err?.response?.data || err?.data || err;
console.error(
'error at validating card form located on payment method gapp',
error
);
setPaymentMethodDataOut({
...paymentMethodDataOut,
errorResponse: err?.response || err,
loading: false,
});
}
};
// @ts-ignore
const verifyCardNumberType = (numberCard: string) => {
const cardTypes: any = {
'Electron': /^(4026|417500|4405|4508|4844|4913|4917)\d+$/,
'Maestro':
/^(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\d+$/,
'Dankort': /^(5019)\d+$/,
'Interpayment': /^(636)\d+$/,
'Union Pay': /^(62|88)\d+$/,
// Visa card numbers start with a 4.
'Visa': /^4[0-9]{6,}$/g,
// MasterCard numbers start with the numbers 51 through 55,
// but this will only detect MasterCard credit cards;
// there are other cards issued using the MasterCard system that
// do not fall into this IIN range. In 2016, they will add numbers
// in the range (222100-272099).
'Master Card':
/^5[1-5][0-9]{5,}|222[1-9][0-9]{3,}|22[3-9][0-9]{4,}|2[3-6][0-9]{5,}|27[01][0-9]{4,}|2720[0-9]{3,}$/g,
// American Express card numbers start with 34 or 37.
'American Express': /^3[47][0-9]{5,}$/g,
// Diners Club card numbers begin with 300 through 305, 36 or 38. There are Diners Club cards that begin with 5 and have 16 digits. These are a joint venture between Diners Club and MasterCard and should be processed like a MasterCard.
'Diner Club': /^3(?:0[0-5]|[68][0-9])[0-9]{4,}$/g,
// Discover card numbers begin with 6011 or 65.
'Discover': /^6(?:011|5[0-9]{2})[0-9]{3,}$/g,
// JCB cards begin with 2131, 1800 or 35.
'JCB': /^(?:2131|1800|35[0-9]{3})[0-9]{3,}$/g,
};
for (const nType in cardTypes) {
if (cardTypes[nType].test(numberCard)) {
return nType;
}
}
};
/** handle dataOut of CardForm */
const handleCardFormDataOut = async (values: any) => {
try {
const validatedCard: any = await validateCreditDebitCard(values);
// if (typeof values?.isSavedCard === 'boolean') {
setPaymentMethodDataOut({
...paymentMethodDataOut,
loading: false,
errorResponse: {},
paymentMethod: tempSelectedPaymentMethod,
cardDetails: Object.assign(values, validatedCard),
});
// } else {
// setPaymentMethodDataOut({
// ...paymentMethodDataOut,
// loading: false,
// errorResponse: {},
// cardDetails: Object.assign(values, validatedCard),
// });
// }
} catch (err: any) {
const error = err?.response?.data || err?.data || err;
console.error(
'error at submitting card details located on payment method gapp',
error
);
setPaymentMethodDataOut({
...paymentMethodDataOut,
errorResponse: err?.response || err,
loading: false,
});
}
};
/** handle dataOut of CardForm */
// const handleCardFormDataOut2 = async (values: any) => {
// try {
// setPaymentMethodDataOut({
// ...paymentMethodDataOut,
// loading: true,
// });
// const validatedCard: any = await validateCreditDebitCard(values);
// const numberCard: string = (values?.cardNumber || '')?.replace(
// /(-)*/g,
// ''
// );
// const cardDetailsValue = {
// cardNumber: numberCard, // '5123-4567-8901-2346',
// cvv: values?.cvv,
// mm: values?.mm,
// name: values?.name,
// yy: values?.yy,
// };
// if (typeof values?.isSavedCard === 'boolean') {
// const privacyCardNumber = numberCard?.replace(/.{12}/g, '**********');
// const savedCardsLength: number = (
// paymentMethodDataOut?.savedCards || []
// )?.length;
// // const hasSavedCards: boolean = savedCardsLength > 0;
// const savedCardNewId: number = savedCardsLength + 1;
// const savedCardDetails = {
// id: savedCardNewId,
// name: verifyCardNumberType(numberCard) || '',
// card_number: privacyCardNumber, // '**********2346',
// promo: '',
// value: cardDetailsValue,
// };
// const cards = paymentMethodDataOut?.savedCards?.concat([
// savedCardDetails,
// ]);
// setPaymentMethodDataOut({
// ...paymentMethodDataOut,
// loading: false,
// errorResponse: {},
// paymentMethod: tempSelectedPaymentMethod,
// cardDetails: Object.assign(values, cardDetailsValue, validatedCard),
// defaultCardId: values?.isSavedCard
// ? values.id
// : paymentMethodDataOut?.defaultCardId,
// savedCards: values?.isSavedCard
// ? cards
// : paymentMethodDataOut?.savedCards,
// });
// } else {
// setPaymentMethodDataOut({
// ...paymentMethodDataOut,
// loading: false,
// errorResponse: {},
// cardDetails: Object.assign(values, cardDetailsValue, validatedCard),
// });
// }
// } catch (err: any) {
// const error = err?.response?.data || err?.data || err;
// console.error(
// 'error at submitting card details located on payment method gapp',
// error
// );
// setPaymentMethodDataOut({
// ...paymentMethodDataOut,
// errorResponse: err?.response || err,
// loading: false,
// });
// }
// };
// const handleSavedCardScreenDataOut = async ({
// defaultId,
// newData,
// newSelected,
// }: {
// defaultId?: number | string;
// newData: any;
// newSelected: any;
// }) => {
// const cardDefaultID = newData.filter((n: any) => n.id === defaultId);
// const cardNotDefaultID = newData.filter((n: any) => n.id !== defaultId);
// setPaymentMethodDataOut({
// ...paymentMethodDataOut,
// defaultCardId: cardDefaultID?.length === 1 ? defaultId : 0,
// savedCards: cardDefaultID.concat(cardNotDefaultID),
// savedCard: newSelected,
// });
// };
// api request and set to state action (case 1 - network-service-config)
const fetchItems = useCallback(async () => {
try {
const networkServiceCondition1 =
dataLoadType === 'network-service-config' && endpoints?.getAll;
const networkServiceCondition2 =
dataLoadType === 'network-service' &&
endpoints?.getAll &&
axiosInstance;
if (networkServiceCondition1) {
setPaymentMethodDataOut({
...paymentMethodDataOut,
errorResponse: {},
loading: true,
});
const res = await httpRequest({
url: `${baseUrl}${endpoints?.getAll}`,
method: 'GET',
bearerToken: token,
tokenType: 'Bearer',
...resDataLoad,
});
setPaymentMethods(res?.data?.data || res?.data || []);
setPaymentMethodDataOut({
...paymentMethodDataOut,
errorResponse: {},
loading: false,
});
} else if (networkServiceCondition2) {
setPaymentMethodDataOut({
...paymentMethodDataOut,
errorResponse: {},
loading: true,
});
const res = await axiosInstance.get(endpoints?.getAll);
setPaymentMethods(res?.data?.data || res?.data || []);
setPaymentMethodDataOut({
...paymentMethodDataOut,
errorResponse: {},
loading: false,
});
}
} catch (err: any) {
const error = err?.response || err;
console.error(
'error at fetching payment method list located on payment method gapp',
error
);
setPaymentMethodDataOut({
...paymentMethodDataOut,
loading: false,
errorResponse: error,
});
}
}, []);
// life-cycle method to run api request
useEffect(() => {
fetchItems();
}, [endpoints?.getAll]);
// life-cycle method to dataOut (your GApp)
useEffect(() => {
dataOut(paymentMethodDataOut);
}, [paymentMethodDataOut]);
// useEffect(() => {
// if (Array.isArray(dataLoad?.savedCards)) {
// setPaymentMethodDataOut({
// ...paymentMethodDataOut,
// savedCards: dataLoad?.savedCards,
// });
// console.log('render dataLoad Saved cards', dataLoad?.savedCards);
// }
// }, [JSON.stringify(dataLoad?.savedCards)]);
return {
/** mini-app/screen key (compatible to: MethodSelection) */
'payment-method': {
dataLoad: constructedData(),
},
/** mini-app/screen key (compatible to: MethodSelectionAccordion) */
'payment-method-accordion-list': {
dataLoad: constructedData(),
dataIn: {
selectedPaymentMethod:
paymentMethodDataOut.paymentMethod[cardFormTriggerKey] ===
cardFormTrigger
? paymentMethodInitialValues
: paymentMethodDataOut.paymentMethod,
},
dataOut: handleSelectPaymentMethod,
},
/** mini-app/screen key (compatible to: MethodSelectionAccordionScreen) */
'payment-method-accordion-screen': {
dataLoad: constructedData(),
dataIn: {
selectedPaymentMethod:
paymentMethodDataOut.paymentMethod[cardFormTriggerKey] ===
cardFormTrigger
? paymentMethodInitialValues
: paymentMethodDataOut.paymentMethod,
cardFormTrigger,
handleCardFormTrigger,
savedCards: dataLoad?.savedCards,
onPressNewCard: handleAddNewCard,
onPressManageCard: dataIn?.actions?.handleManageCard,
},
dataOut: handleSubmitPaymentMethodAccordion,
},
/** mini-app/screen key (compatible to: MethodSelectionAccordionScreen) */
// 'payment-method-accordion-screen2': {
// dataLoad: constructedData(),
// dataIn: {
// selectedPaymentMethod:
// paymentMethodDataOut.paymentMethod?.text === cardFormTrigger
// ? paymentMethodInitialValues
// : paymentMethodDataOut.paymentMethod,
// cardFormTrigger,
// handleCardFormTrigger,
// savedCards: paymentMethodDataOut?.savedCards,
// onPressNewCard: handleAddNewCard,
// onPressManageCard: dataIn?.actions?.handleManageCard,
// },
// dataOut: handleSubmitPaymentMethodAccordion,
// },
/** mini-app/screen key (compatible to: CardForm) */
'card-form-details': {
dataOut: handleCardFormDataOut,
},
/** mini-app/screen key (compatible to: CardForm) */
// 'card-form-details2': {
// dataOut: handleCardFormDataOut2,
// },
/** mini-app/screen key (compatible to: SavedCardScreen) */
'saved-card-screen': {
dataLoad: {
data: dataLoad?.savedCards,
},
},
// 'saved-card-screen-2': {
// dataLoad: {
// data: paymentMethodDataOut?.savedCards,
// },
// dataOut: handleSavedCardScreenDataOut,
// },
};
}
export default useViewModel;