...
Example of use: frontend/src/state/payment/hooks/use-payment.tsx
Task 2: Add the new payment method in the payment state and structure
Add Bizum in IPaymentMethod
Expand |
---|
title | frontend/src/state/payment/types.ts |
---|
|
Code Block |
---|
| export interface IPaymentMethod {
accountIdentifier: string | null;
fdAccountId: string | null;
paymentMethodBrand?: string | null;
chaseProfileId: string | null;
paypalIdentifier: string | null;
credit: ICredit | null;
selected?: boolean;
prepaid: IPrepaid | null;
// make cash required eventually
cash?: boolean | null;
paypal: boolean | null;
ideal: boolean | null;
blik?: boolean | null;
sodexo?: boolean | null;
chequeGourmet?: boolean | null;
paymentOnDeliveryCard?: boolean | null;
bizum?: boolean | null;
transient?: boolean;
} |
|
Create a new placeholder for the new payment method
Expand |
---|
title | frontend/src/state/payment/constants.ts |
---|
|
Code Block |
---|
| export const BIZUM_PAYMENT_METHOD_PLACEHOLDER: IPaymentMethod = {
bizum: true,
fdAccountId: 'BIZUM',
accountIdentifier: 'BIZUM',
paymentMethodBrand: 'BIZUM',
chaseProfileId: null,
credit: null,
prepaid: null,
paypalIdentifier: null,
ideal: null,
paypal: false,
sodexo: null,
chequeGourmet: false,
transient: true,
}; |
|
Add the new payment method in getPaymentMethodsState
:
Expand |
---|
title | frontend/src/state/payment/hooks/getPaymentMethodsState/index.ts |
---|
|
Update the interface: frontend/src/state/payment/hooks/types.ts :
Code Block |
---|
| export interface ISetupPaymentMethodState {
enableBizum: boolean;
} |
Adjust the getPaymentMethodsState : Code Block |
---|
| export const getPaymentMethodsState = ({
// rest of the code
enableNewMethod,
}: ISetupPaymentMethodState) => {
// rest of the code
if (enableBizum) {
availablePaymentMethodList.push(BIZUM_PAYMENT_METHOD_PLACEHOLDER);
}
// rest of the code
}; |
|
Expand |
---|
title | frontend/src/state/payment/hooks/use-payment.tsx |
---|
|
Add the new feature flag here and then pass through the getPaymentMethodState : Code Block |
---|
| const usePayment = ({
// rest of the code
const enableBizum = useFlag(LaunchDarklyFlag.ENABLE_BIZUM_PAYCOMET); // new line
const initPaymentMethods = useCallback(() => {
const {
availablePaymentMethodList,
validDefaultPaymentMethodId,
validDefaultReloadPaymentMethodId,
} = getPaymentMethodsState({
enableBizum, // new line here
});
},
[]
);
}); |
Adjust the unit tests mocking the new flag where necessary: frontend/src/state/payment/tests/use-payment.test.tsx |
Adjust IPaycometState interface
Expand |
---|
title | workspaces/frontend/src/utils/payment/index.ts |
---|
|
Update isPayPay to isPayLink and make a refactor code. Code Block |
---|
| export interface IPaycometState extends IPaymentState {
merchantAccount?: string;
pspReference?: string;
jetId?: string;
iframeUrl?: string;
isPayLink?: boolean;
card?: {
expiryMonth: string;
expiryYear: string;
bin: string;
last4: string;
};
paymentMethodBrand?: PaymentMethodBrand;
paytpvToken?: string;
} |
|
Adjust use-order-payment
hook
Expand |
---|
title | frontend/src/pages/cart/payment/order-payment/use-order-payment.ts |
---|
|
Add a new state for the new payment method: const [isPaycometBizumMethodSelected, setIsPaycometBizumMethodSelected] = useState(false);
Update the handlePaymentMethodSelected adding the new method: Code Block |
---|
| const handlePaymentMethodSelected = (newFdAccountId?: string) => {
if (!newFdAccountId) {
payment.setCheckoutPaymentMethodId('');
return;
}
// rest of the code
setIsPaycometBizumMethodSelected(false); // New line
}; |
Clear our new method in the useEffect logic that already exist: Code Block |
---|
| // 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
setIsPaycometBizumMethodSelected(false);
}
}, [setCheckoutPaymentMethodId, checkoutPaymentMethodId]); |
Update the placeOrder function to deal with the new method: Adjust the processOrderWithAccount function for the new method like this Sodexo example: Code Block |
---|
| // 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: Code Block |
---|
| // 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();
} |
|
Extend the getCurrentSelectedMethodType
:
Expand |
---|
title | frontend/src/components/payment-method/utils/get-current-selected-method-type.ts |
---|
|
Add the new payment in the enum CurrentSelectedMethodType : Code Block |
---|
| export enum CurrentSelectedMethodType {
// rest of the code
ADD_NEW_PAYMENT_BIZUM = 'addNewPaymentBizum',
} |
Adjust the IPaymentMethodProps : Code Block |
---|
| export interface IPaymentMethodProps {
// rest of the code
setIsPaycometBizumMethodSelected?: Dispatch<SetStateAction<boolean>>;
isPaycometBizumMethodSelected?: boolean;
} |
Add the new condition in getCurrentSelectedMethodType : Code Block |
---|
type GetCurrentSelectedMethodTypeParams = {
// rest of the code
isPaycometBizumMethodSelected?: boolean;
};
function getCurrentSelectedMethodType({
// rest of the code
isPaycometNewMethodSelected
}: GetCurrentSelectedMethodTypeParams): CurrentSelectedMethodType {
let currentSelectedMethodType = CurrentSelectedMethodType.ADD_NEW_PAYMENT_METHOD;
// rest of the code
if (isPaycometBizumMethodSelected) {
currentSelectedMethodType = CurrentSelectedMethodType.ADD_NEW_PAYMENT_BIZUM;
}
// rest of the code
} |
|
Adjust order-payment comp:
Expand |
---|
title | frontend/src/pages/cart/payment/order-payment/payment.tsx |
---|
|
Return the new create state from useOrderPayment : Code Block |
---|
| const {
// rest of the code
isPaycometBizumMethodSelected, // new line here
} = useOrderPayment({
serverOrder,
onPriceOrder,
enableOrderTimedFire,
}); |
Pass the returned value to the getCurrentSelectedMethodType : Code Block |
---|
| const currentSelectedMethodType = getCurrentSelectedMethodType({
isPaycometBizumMethodSelected,
}); |
Pass the new method to the <PaymentMethod> comp: Code Block |
---|
| <PaymentMethod
// rest of the code
isPaycometBizumMethodSelected={isPaycometBizumMethodSelected}
/> |
|
Task 3: Add the new method in payment-method structure
Create new styled component for the new payment method
Expand |
---|
title | frontend/src/components/payment-method/styled.ts |
---|
|
Code Block |
---|
| // ... rest of the file
export const StyledBizumMethodCardIcon = styled(IconNewMethod)`
${cardIconSize}
`; |
|
Expand |
---|
title | frontend/src/components/payment-method/payment-method-options/types.ts |
---|
|
Code Block |
---|
| export type PaymentMethodOptionProps = {
// ... rest of the file
setIsPaycometBizumMethodSelected?: Dispatch<SetStateAction<boolean>>;
}; |
|
Add the new method in payment-method comp
Expand |
---|
title | workspaces/frontend/src/components/payment-method/index.tsx |
---|
|
Add new props in PaymentMethod : Code Block |
---|
|
function PaymentMethod({
// rest of the code
isPaycometBizumMethodSelected,
setIsPaycometBizumMethodSelected,
}: IPaymentMethodProps) {
// rest of the code
// pass the new prop in the getCurrentSelectedMethodType
const currentSelectedMethodType = getCurrentSelectedMethodType({
isPaycometBizumMethodSelected
});
}); |
Create new item inside PaymentMethod comp: Code Block |
---|
| // ... rest of the file
const SelectedNewMethodOptionItem = (
<PaymentMethodAddNewItemOption
data-testid="payment-bizum-method-name"
text={formatMessage({ id: 'addNewPaymentBizumMethod' })}
Icon={<StyledNewMethodCardIcon />}
onClick={getSelectedMethodClickHandler()}
selected
/>
); |
Add the new method in the map structure: Code Block |
---|
const CurrentSelectedMethodMap = {
// ... rest of the file
[CurrentSelectedMethodType.ADD_NEW_PAYMENT_BIZUM]: SelectedBizumOptionItem,
}; |
Pass the new prop in the <PaymentMethodOptions> comp: Code Block |
---|
| <PaymentMethodOptions
setIsPaycometBizumMethodSelected={setIsPaycometBizumMethodSelected}
/> |
|
Add the new method in payment-method-options comp
Expand |
---|
title | frontend/src/components/payment-method/payment-method-options/payment-method-options.tsx |
---|
|
Add the new prop in PaymentMethodOptions comp: Code Block |
---|
| function PaymentMethodOptions({
// .. rest of the file
setIsPaycometNewMethodSelected
}: PaymentMethodOptionProps) {
const enableBizumMethod = useFlag(LaunchDarklyFlag.ENABLE_BIZUM_PAYCOMET); // new line
}); |
Add new condition in handleUnselectedMethodClick , handleAddNewCardOptionClick , handleAddNewGiftCardOptionClick , handlePaypalOptionClick and handleAdyenIdealOptionClick to clear our new state: Code Block |
---|
| if (setIsPaycometSodexoOptionSelected) {
setIsPaycometBizumMethodSelected(false);
} |
Add a new handle for our new method (using useCallback) where we’ll clear the other methods and set your: Code Block |
---|
| const handleBizumOptionClick = useCallback(
(e: React.FormEvent<HTMLButtonElement>) => {
e.preventDefault();
setIsAddNewCardOptionSelected(false);
setIsAddNewGiftCardOptionSelected(false);
if (setIsPaycometBizumOptionSelected) {
setIsPaycometBizumOptionSelected(true);
}
if (setIsPaycometPaypalOptionSelected) {
setIsPaycometPaypalOptionSelected(false);
}
if (setIsAdyenIdealOptionSelected) {
setIsAdyenIdealOptionSelected(false);
}
if (setIsAdyenBlikOptionSelected) {
setIsAdyenBlikOptionSelected(false);
}
if (setIsPaycometSodexoOptionSelected) {
setIsPaycometSodexoOptionSelected('');
}
if (setIsPaycometChequeGourmetOptionSelected) {
setIsPaycometChequeGourmetOptionSelected('');
}
openPaycometPayLinklPopup('BIZUM');
},
[
setIsAddNewCardOptionSelected,
setIsAddNewGiftCardOptionSelected,
setIsPaycometBizumOptionSelected,
setIsPaycometPaypalOptionSelected,
setIsAdyenIdealOptionSelected,
setIsAdyenBlikOptionSelected,
setIsPaycometSodexoOptionSelected,
setIsPaycometChequeGourmetOptionSelected,
openPaycometPayLinklPopup,
]
); |
Add in the enableSortPaymentMethods && unSelectedMethods.map a new block for our new method: Code Block |
---|
| {method?.bizum && method?.transient && (
<StyledOption>
<PaymentMethodOptionItemDivider />
<PaymentMethodAddNewItemOption
data-testid="add-bizum-payment"
text={'Bizum'}
Icon={<StyledBizumCardIcon />} // should create this icon
onClick={handleBizumOptionClick}
isLoading={isLoadingPopup}
/>
</StyledOption>
)} |
Add a new block for for the case where we not have the enableSortPaymentMethods as true: Code Block |
---|
| {!enableSortPaymentMethods && isBizumEnabled && (
<StyledOption>
<PaymentMethodOptionItemDivider />
<PaymentMethodAddNewItemOption
data-testid="add-bizum-payment"
text={formatMessage({ id: bizumlLabel(isGuestOrder) })}
Icon={<StyledPaypalCardIcon />}
onClick={handlePaypalOptionClick}
isLoading={isLoadingPopup}
/>
</StyledOption>
)} |
Add the new payment method in the sortPaymentMethods Code Block |
---|
| 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 'BIZUM':
return paymentMethods.filter(
p => !p?.transient && p?.bizum && 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: Code Block |
---|
| const isNotLocalWallet = (method: IPaymentMethod) => {
return !method.blik && !method.ideal;
};
const sortedMethodsFilterConditions = (method: IPaymentMethod) => {
if (method.bizum) {
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 => { |
|
Add the new method in paycomet-hosted-page-payment comp
Expand |
---|
title | frontend/src/pages/cart/payment/order-payment/paycomet-hosted-page-payment/paycomet-hosted-page-payment.tsx |
---|
|
Return the new values from the useOrderPayment hook Code Block |
---|
| const {
// rest of code
setIsPaycometBizumMethodSelected,
isPaycometBizumMethodSelected,
} = useOrderPayment({
enableOrderTimedFire,
onPriceOrder,
serverOrder,
}); |
Pass down the values to the <PaymentMethod> comp Code Block |
---|
| // rest of the code
<PaymentInfoBackground $hostedPayment>
{showPaymentMethodSelection && (
<PaymentMethod
// rest of the code
setIsPaycometBizumMethodSelected={setIsPaycometBizumMethodSelected} // new line
isPaycometBizumMethodSelected={isPaycometBizumMethodSelected} // 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:
Expand |
---|
title | frontend/src/state/payment/types.ts |
---|
|
Code Block |
---|
| export interface IPaymentMethod {
// rest of the code
bizumPaymentMethod?: boolean | null;
} |
|
Add new file in frontend/src/components/payment-method-option/
as the following example:
Expand |
---|
title | frontend/src/components/payment-method-option/new-payment-method.tsx (example of new file) |
---|
|
Code Block |
---|
| import React from 'react';
import { useIntl } from 'react-intl';
import { MethodType } from './styled';
const NewPaymentMethod = () => { // Example SodexoMethod
const { formatMessage } = useIntl();
return <MethodType>{formatMessage({ id: 'payWithBizumMethod' })}</MethodType>;
};
export default NewPaymentMethod; |
|
Use the created payment method option in payment-method-option
:
Expand |
---|
title | frontend/src/components/payment-method-option/index.tsx |
---|
|
Adjust the RenderMethodType and add the new method created above Code Block |
---|
|
const RenderMethodType = () => {
// rest of the code
// New if here
if (method.bizumPaymentMethod) {
return <BizumPaymentMethod />;
}
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.bizumPaymentMethod && <StyledBizumPaymentMethodCardIcon />}
</MethodTypeDescription>
// rest of the code
</div>
</MethodTypeWrapper>
// rest of the code
); |
|
Task 5: Adjust account payment method lists to deal with the new method
...
Expand |
---|
title | 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: Code Block |
---|
| 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 !== BIZUM_PAYMENT_METHOD_PLACEHOLDER.accountIdentifier &&
);
} |
|
Task 7: Adjust the receipt email to show the correct message for the new payment method
...
Today, when the commit order is processed, order-service triggers Braze API with the order info, that then sends the confirmation email to the customer.
We can create a validation when cardType different CASH and ccLast4 is empty.
Expand |
---|
title | packages/emails/src/utils.ts |
---|
|
Code Block |
---|
| export const transformOrderProperties = ({
rbiOrder,
brand,
fzStore,
language,
}: ITransformOrderProperties): Record<string, any> => {
...
const { cardType, ccLast4, paymentType, paymentMethodBrand }: Partial<IPayment> = payment || {};
let last4 = ccLast4;
if (cardType !== RBIPaymentCard.CASH && !last4?.trim().length) {
last4 = '****';
}
...
return properties;
} |
|
After we change intl-packages/email to send the necessary information to Braze API, we need to update the templates configured, which are split into templates and content blocks (reusable pieces of content). Using PLK-ES as example we can see:
Note |
---|
The example displays a LiquidJS template, which is similar but not equal to Braze templates. Braze access properties using the syntax {{event_properties.${language}}} |