You are viewing an old version of this page. View the current version.
Compare with Current
View Page History
« Previous
Version 12
Next »
Repos that we’ll change:
Whitelabel: https://github.com/rbilabs/intl-whitelabel-app
Figma: Schroer, Gabriel (Deactivated)
Task summary - Whitelabel:
Components tree architecture
Tasks breakdown
The solution was based on the Sodexo Cheque Gourmet Voucher implementation: https://github.com/rbilabs/intl-whitelabel-app/pull/686/files
Task 1: Create a new feature flag
Flag should be added in: frontend/src/utils/launchdarkly/flags.ts
Suggestion name: ENABLE_CARD_AT_HOME_PAYCOMET
Task 2: Add the new payment method in the payment state and structure
frontend/src/state/payment/constants.ts
// Replace this name for what we decide
export const NEW_PAYMENT_METHOD_PLACEHOLDER: IPaymentMethod = {
sodexo: false,
fdAccountId: 'CASH',
accountIdentifier: 'NEW_PAYMENT_METHOD', // TO BE DEFINED
paymentMethodBrand: 'NEW_PAYMENT_METHOD', // TO BE DEFINED
chaseProfileId: null,
credit: null,
prepaid: null,
paypalIdentifier: null,
ideal: null,
paypal: false,
};
frontend/src/state/payment/hooks/getPaymentMethodsState/index.ts
Update the interface:
frontend/src/state/payment/hooks/types.ts
:
export interface ISetupPaymentMethodState {
enableNewMethod: boolean;
}
Adjust the getPaymentMethodsState
:
export const getPaymentMethodsState = ({
// rest of the code
enableNewMethod,
}: ISetupPaymentMethodState) => {
// rest of the code
if (enableNewMethod) {
availablePaymentMethodList.push(NEW_PAYMENT_METHOD_PLACEHOLDER);
}
// rest of the code
};
frontend/src/state/payment/hooks/use-payment.tsx
Update cash account variable:
const cashAccount: IPaymentMethod = {
// rest of the code
newMethod: null,
};
Add the new feature flag here and then pass through the getPaymentMethodState
:
const usePayment = ({
// rest of the code
const enableNewMethod = useFlag(LaunchDarklyFlag.ENABLE_CREDIT_CARD_AT_HOME_PAYCOMET); // new line
const initPaymentMethods = useCallback(() => {
const {
availablePaymentMethodList,
validDefaultPaymentMethodId,
validDefaultReloadPaymentMethodId,
} = getPaymentMethodsState({
enableNewMethod, // new line here
});
},
[]
);
});
Adjust the unit tests mocking the new flag where necessary: frontend/src/state/payment/tests/use-payment.test.tsx
frontend/src/pages/cart/payment/order-payment/use-order-payment.ts
Add a new state for the new payment method:
const [isPaycometNewMethodSelected, setIsPaycometNewMethodSelected] = useState('');
Update the handlePaymentMethodSelected
adding the new method:
const handlePaymentMethodSelected = (newFdAccountId?: string) => {
if (!newFdAccountId) {
payment.setCheckoutPaymentMethodId('');
return;
}
// rest of the code
setIsPaycometNewMethodSelected(''); // New line
};
Clear our new method in the useEffect
logic that already exist:
// when checkout payment method have been updated from payment context
// the flags to show add new credit card or add gift card
// have to be updated.
useEffect(() => {
// rest of the code
setIsPaycometNewMethodSelected('');
}
}, [setCheckoutPaymentMethodId, checkoutPaymentMethodId]);
Update the placeOrder
function to deal with the new method:
Adjust the processOrderWithAccount
function for the new method like this Sodexo example:
// rest of the code
const isPaymentWithVoucher =
paymentAccount.paymentMethodBrand === PaymentMethodBrand.SODEXO_VOUCHER ||
paymentAccount.paymentMethodBrand === PaymentMethodBrand.CHEQUE_GOURMET_VOUCHER;
if (payment.checkoutPaymentMethodId === CASH_ACCOUNT_IDENTIFIER || isPaymentWithVoucher) {
const paymentDetails: IPayment = {
cashPayment: true,
fullName,
paymentMethodBrand: paymentAccount?.paymentMethodBrand ?? undefined,
};
if (payment.isVrPayment) {
// The GraphQL endpoint for VR Payment expects this object even
// for Cash orders, otherwise the order creation fails
paymentDetails.vrPaymentInput = {
merchantAccount: '',
pspReference: '',
storePaymentMethod: true,
};
}
return commitOrder({
...commitInput,
creditType: CASH_ACCOUNT_IDENTIFIER,
payment: paymentDetails,
});
}
Adjust the handleOrderCommit
to place the order with the new method:
// Add new condition here as the isPaymentWithVoucher from the Sodexo example
const isPaymentWithVoucher = isPaymentWithSodexoVoucher || isPaymentWithChequeGourmetVoucher;
if (
(showAddPaymentMethod ||
isAdyenBlikMethodSelected ||
reloadAddPaymentMethod ||
isPayPalRegistration) &&
!(selectedPaymentMethod?.cash || isPaymentWithVoucher || payment.isFreeOrderPayment)
) {
await placeOrder();
} else {
await placeOrderWithAccount();
}
frontend/src/components/payment-method/utils/get-current-selected-method-type.ts
Add the new payment in the enum CurrentSelectedMethodType
:
export enum CurrentSelectedMethodType {
// rest of the code
ADD_NEW_PAYMENT_NEW_METHOD = 'addNewPaymentNewMethod',
}
Adjust the IPaymentMethodProps
:
export interface IPaymentMethodProps {
// rest of the code
setIsPaycometNewMethodSelected?: Dispatch<SetStateAction<string>>;
isPaycometNewMethodSelected?: string;
}
Add the new condition in getCurrentSelectedMethodType
:
type GetCurrentSelectedMethodTypeParams = {
// rest of the code
isPaycometNewMethodSelected?: string;
};
function getCurrentSelectedMethodType({
// rest of the code
isPaycometNewMethodSelected
}: GetCurrentSelectedMethodTypeParams): CurrentSelectedMethodType {
let currentSelectedMethodType = CurrentSelectedMethodType.ADD_NEW_PAYMENT_METHOD;
// rest of the code
if (isPaycometNewMethodSelected === 'card') {
currentSelectedMethodType = CurrentSelectedMethodType.ADD_NEW_PAYMENT_NEW_METHOD;
}
// rest of the code
}
frontend/src/pages/cart/payment/order-payment/payment.tsx
Return the new create state from useOrderPayment
:
const {
// rest of the code
isPaycometNewMethodSelected, // new line here
} = useOrderPayment({
serverOrder,
onPriceOrder,
enableOrderTimedFire,
});
Pass the returned value to the getCurrentSelectedMethodType
:
const currentSelectedMethodType = getCurrentSelectedMethodType({
isPaycometNewMethodSelected,
});
Pass the new method to the <PaymentMethod>
comp:
<PaymentMethod
// rest of the code
isPaycometNewMethodSelected={isPaycometNewMethodSelected}
/>
Task 3: Add the new method in payment-method structure
frontend/src/components/payment-method/styled.ts
// ... rest of the file
export const StyledNewMethodCardIcon = styled(IconNewMethod)`
${cardIconSize}
`;
frontend/src/components/payment-method/payment-method-options/types.ts
export type PaymentMethodOptionProps = {
// ... rest of the file
setIsPaycometNewMethodSelected?: Dispatch<SetStateAction<string>>;
};
workspaces/frontend/src/components/payment-method/index.tsx
Add new props in PaymentMethod
:
function PaymentMethod({
// rest of the code
isPaycometNewMethodSelected,
setIsPaycometNewMethodSelected,
}: IPaymentMethodProps) {
// rest of the code
// pass the new prop in the getCurrentSelectedMethodType
const currentSelectedMethodType = getCurrentSelectedMethodType({
isPaycometNewMethodSelected
});
});
Create new item inside PaymentMethod
comp:
// ... rest of the file
const SelectedNewMethodOptionItem = (
<PaymentMethodAddNewItemOption
data-testid="payment-new-method-name"
text={formatMessage({ id: 'addNewPaymentNewMethod' })}
Icon={<StyledNewMethodCardIcon />}
onClick={getSelectedMethodClickHandler()}
selected
/>
);
Add the new method in the map structure:
const CurrentSelectedMethodMap = {
// ... rest of the file
[CurrentSelectedMethodType.ADD_NEW_PAYMENT_METHOD]: SelectedNewMethodOptionItem,
};
Pass the new prop in the <PaymentMethodOptions>
comp:
<PaymentMethodOptions
setIsPaycometNewMethodSelected={setIsPaycometNewMethodSelected}
/>
frontend/src/components/payment-method/payment-method-options/payment-method-options.tsx
Add the new prop in PaymentMethodOptions
comp:
function PaymentMethodOptions({
// .. rest of the file
setIsPaycometNewMethodSelected
}: PaymentMethodOptionProps) {
const enableNewMethod = useFlag(LaunchDarklyFlag.ENABLE_CREDIT_CARD_AT_HOME_PAYCOMET); // new line
});
Add new condition in handleUnselectedMethodClick
, handleAddNewCardOptionClick
, handleAddNewGiftCardOptionClick
, handlePaypalOptionClick
and handleAdyenIdealOptionClick
to clear our new state:
if (setIsPaycometSodexoOptionSelected) {
setIsPaycometNewMethodSelected('');
}
Add a new handle for our new method (using useCallback) where we’ll clear the other methods and set your:
const handleNewMethodOptionClick = useCallback(
(e: React.FormEvent<HTMLButtonElement>, newMethodType: string) => {
e.preventDefault();
setIsAddNewCardOptionSelected(false);
setIsAddNewGiftCardOptionSelected(false);
if (setIsPaycometPaypalOptionSelected) {
setIsPaycometPaypalOptionSelected(false);
}
if (setIsAdyenIdealOptionSelected) {
setIsAdyenIdealOptionSelected(false);
}
if (setIsAdyenBlikOptionSelected) {
setIsAdyenBlikOptionSelected(false);
}
if (setIsPaycometSodexoOptionSelected) {
setIsPaycometSodexoOptionSelected('');
}
if (setIsPaycometSodexoOptionSelected) {
setIsPaycometSodexoOptionSelected('');
}
// our new method here
if (setIsPaycometNewMethodSelected) {
setIsPaycometNewMethodSelected(newMethodType);
if (newMethodType === 'card') {
setIsAddNewCardOptionSelected(true);
}
}
handleSelect();
setCollapsed(true);
},
[
// callback dependencies here
]
);
Add in the enableSortPaymentMethods && unSelectedMethods.map
a new block for our new method:
const isDelivery = serverOrder?.delivery;
{method?.newMethod && method?.transient && isDelivery && (
<StyledOption>
<PaymentMethodOptionItemDivider />
<PaymentMethodAddNewItemOption
data-testid="add-new-method-payment"
text={formatMessage({ id: newMethodLabel(isGuestOrder) })}
Icon={<StyledNewMethodCardIcon />}
onClick={e => handleNewMethodOptionClick(e, 'card')}
isLoading={isLoadingPopup}
/>
</StyledOption>
)}
Add a new block for for the case where we not have the enableSortPaymentMethods
as true:
{!enableSortPaymentMethods && enableNewMethod && (
<StyledOption>
<PaymentMethodOptionItemDivider />
<PaymentMethodAddNewItemOption
data-testid=add-new-method-payment"
text={formatMessage({ id: newMethodLabel(isGuestOrder) })}
Icon={<StyledNewMethodCardIcon />}
onClick={e => handleNewMethodOptionClick(e, 'card')}
isLoading={isLoadingPopup}
/>
</StyledOption>
)}
Add the new payment method in the sortPaymentMethods
const paymentMethodListTypeOrder = [
// rest of the code
'NEW_METHOD',
];
// Add new case in switch for our new method
function filterMethodByType(typeOrder: string, paymentMethods: IPaymentMethod[]): IPaymentMethod[] {
switch (typeOrder) {
// rest of the code
case 'NEW_METHOD':
return paymentMethods.filter(
p => !p?.transient && p?.newMethod && p.accountIdentifier === typeOrder
);
// rest of the code
}
}
Add a new logic to the filter !enableSortPaymentMethods && unSelectedMethods.filter(isNotLocalWallet)...
to now show our payment method if delivery mode is not selected:
const isDelivery = serverOrder?.delivery;
const isNotLocalWallet = (method: IPaymentMethod) => {
return !method.blik && !method.ideal;
};
const sortedMethodsFilterConditions = (method: IPaymentMethod) => {
if (method.chequeGourmet && !isDelivery) {
return false; // remove our item from the list if delivery is not selected
}
return isNotLocalWallet(method);
};
// A suggestion would be the creation of a new function for the filter conditions
{!enableSortPaymentMethods &&
unSelectedMethods.filter(sortedMethodsFilterConditions).map(method => {
frontend/src/pages/cart/payment/order-payment/paycomet-hosted-page-payment/paycomet-hosted-page-payment.tsx
Return the new values from the useOrderPayment
hook
const {
// rest of code
setIsPaycometNewMethodSelected,
isPaycometNewMethodSelected,
} = useOrderPayment({
enableOrderTimedFire,
onPriceOrder,
serverOrder,
});
Pass down the values to the <PaymentMethod> comp
// rest of the code
<PaymentInfoBackground $hostedPayment>
{showPaymentMethodSelection && (
<PaymentMethod
// rest of the code
setIsPaycometNewMethodSelected={setIsPaycometNewMethodSelected} // new line
isPaycometNewMethodSelected={isPaycometNewMethodSelected} // new line
onResult={onPaymentOutcome}
/>
)}
Task 4: Create and add a new method in payment-method-option structure
Add the new icon for the new payment method in: frontend/src/components/icons/new-payment-method/index.tsx
(replace “new-payment-method” for the new name, hahaha).
Add the new method option in the interface of payment methods:
frontend/src/state/payment/types.ts
export interface IPaymentMethod {
// rest of the code
newPaymentMethod?: boolean | null;
}
frontend/src/components/payment-method-option/new-payment-method.tsx (example of new file)
import React from 'react';
import { useIntl } from 'react-intl';
import { MethodType } from './styled';
const NewPaymentMethod = () => { // Example SodexoMethod
const { formatMessage } = useIntl();
return <MethodType>{formatMessage({ id: 'payWithNewMethod' })}</MethodType>;
};
export default NewPaymentMethod;
frontend/src/components/payment-method-option/index.tsx
Adjust the RenderMethodType
and add the new method created above
const RenderMethodType = () => {
// rest of the code
// New if here
if (method.newPaymentMethod) {
return <NewPaymentMethod />;
}
return null;
};
// add the new payment method inside the <MethodTypeWrapper> in the return
return
// rest of the code
(
// rest of the code
<MethodTypeWrapper
data-private
data-dd-privacy="mask"
onClick={onClickMethod}
$isClickable={!!onClick}
disableMethod={disableMethod}
fromCheckout={fromCheckout}
data-testid={`method-type-wrapper-${method.accountIdentifier ?? method.fdAccountId ?? ''}`}
selected={selected}
>
// rest of the code
<div>
<MethodTypeDescription>
// Add new method here inside the description
{method.newPaymentMethod && <StyledNewPaymentMethodCardIcon />}
</MethodTypeDescription>
// rest of the code
</div>
</MethodTypeWrapper>
// rest of the code
);
Task 5: Adjust account payment method lists to deal with the new method
frontend/src/components/payment-method-flat-list-with-button/payment-method-flat-list-with-button.tsx
We’ll exclude the new payment method if it is not actually stored in the user account:
function filterOutUnsupportedPaymentMethods(paymentMethods: IPaymentMethod[]) {
return paymentMethods.filter(
n =>
n.accountIdentifier !== PAYPAL_PAYMENT_METHOD_PLACEHOLDER.accountIdentifier &&
n.accountIdentifier !== SODEXO_VOUCHER_PAYMENT_METHOD_PLACEHOLDER.accountIdentifier &&
n.accountIdentifier !== NEW_PAYMENT_METHOD_PLACEHOLDER.accountIdentifier &&
);
}
Add Comment