Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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
titlefrontend/src/state/payment/types.ts
Code Block
languagetypescript
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

    • The idea is to send this new payment method identified as cash

Expand
titlefrontend/src/state/payment/constants.ts
Code Block
languagetypescript
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
titlefrontend/src/state/payment/hooks/getPaymentMethodsState/index.ts

Update the interface:

frontend/src/state/payment/hooks/types.ts:

Code Block
languagetypescript
export interface ISetupPaymentMethodState {
  enableBizum: boolean;
}

Adjust the getPaymentMethodsState:

Code Block
languagetypescript
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
};

  • Adjust the payment hook

Expand
titlefrontend/src/state/payment/hooks/use-payment.tsx

Add the new feature flag here and then pass through the getPaymentMethodState:

Code Block
languagetypescript
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
titleworkspaces/frontend/src/utils/payment/index.ts

Update isPayPay to isPayLink and make a refactor code.

Code Block
languagetypescript
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
titlefrontend/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
languagetypescript
  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
languagetypescript
  // 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 if condition for paycomet processor to using isPayLink

    Code Block
    languagetypescript
    } else if (payment.isPaycomet) {
            // rest of the code
            if (payCometValues.isPayLink) {
                commitInput = {
                creditType: payCometValues.cardType,
                order,
                payment: {
                  fullName: payCometValues.nameOnCard || '',
                  ccMetadata: undefined,
                  paycometInput: {
                    pspReference: payCometValues.pspReference || '',
                    storePaymentMethod: payCometValues.saveCard || false,
                    storedPaymentMethodId: payCometValues.cardNumber || undefined,
                  },
                },
                skipCoolingPeriod: true,
              };
            } else {
              ...
          }
      // rest of the code

Adjust the processOrderWithAccount function for the new method like this Sodexo example:

Code Block
languagetypescript
        // 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
languagetypescript
      // 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
titlefrontend/src/components/payment-method/utils/get-current-selected-method-type.ts

Add the new payment in the enum CurrentSelectedMethodType:

  • frontend/src/components/payment-method/types.ts:

Code Block
languagetypescript
export enum CurrentSelectedMethodType {
  // rest of the code
  ADD_NEW_PAYMENT_BIZUM = 'addNewPaymentBizum',
}

Adjust the IPaymentMethodProps:

Code Block
languagetypescript
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
titlefrontend/src/pages/cart/payment/order-payment/payment.tsx

Return the new create state from useOrderPayment:

Code Block
languagetypescript
  const {
   // rest of the code
   isPaycometBizumMethodSelected, // new line here
  } = useOrderPayment({
    serverOrder,
    onPriceOrder,
    enableOrderTimedFire,
  });

Pass the returned value to the getCurrentSelectedMethodType:

Code Block
languagetypescript
  const currentSelectedMethodType = getCurrentSelectedMethodType({
    isPaycometBizumMethodSelected,
  });

Pass the new method to the <PaymentMethod> comp:

Code Block
languagetypescript
<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
titlefrontend/src/components/payment-method/styled.ts
Code Block
languagetypescript
// ... rest of the file
export const StyledBizumMethodCardIcon = styled(IconNewMethod)`
  ${cardIconSize}
`;
  • Add a new type

Expand
titlefrontend/src/components/payment-method/payment-method-options/types.ts
Code Block
languagetypescript
export type PaymentMethodOptionProps = {
  // ... rest of the file
  setIsPaycometBizumMethodSelected?: Dispatch<SetStateAction<boolean>>;
};
  • Add the new method in payment-method comp

Expand
titleworkspaces/frontend/src/components/payment-method/index.tsx

Add new props in PaymentMethod:

Code Block
languagetypescript

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
languagejsx
// ... 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
languagetsx
 <PaymentMethodOptions
    setIsPaycometBizumMethodSelected={setIsPaycometBizumMethodSelected}
  />
  • Add the new method in payment-method-options comp

Expand
titlefrontend/src/components/payment-method/payment-method-options/payment-method-options.tsx

Add the new prop in PaymentMethodOptions comp:

Code Block
languagetypescript
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
languagetypescript
  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
languagetypescript
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:

  • We also need to condition this payment method to be shown

Code Block
languagetypescript
{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
languagetsx
{!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

  • frontend/src/components/payment-method/utils/sort-payment-methods.ts

Code Block
languagetypescript
  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
languagetypescript
  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
titlefrontend/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
languagetypescript
const {
    // rest of code
    setIsPaycometBizumMethodSelected,
    isPaycometBizumMethodSelected,
  } = useOrderPayment({
    enableOrderTimedFire,
    onPriceOrder,
    serverOrder,
  });

Pass down the values to the <PaymentMethod> comp

Code Block
languagetsx
// 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
titlefrontend/src/state/payment/types.ts
Code Block
languagetypescript
export interface IPaymentMethod {
  // rest of the code
  bizumPaymentMethod?: boolean | null;
}

  • Add new file in frontend/src/components/payment-method-option/as the following example:

    • Remember to add the translation that will be used in formatMessage

Expand
titlefrontend/src/components/payment-method-option/new-payment-method.tsx (example of new file)
Code Block
languagetypescript
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
titlefrontend/src/components/payment-method-option/index.tsx

Adjust the RenderMethodType and add the new method created above

Code Block
languagetsx

  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
titlefrontend/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
languagetypescript
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
titlepackages/emails/src/utils.ts
Code Block
languagejs
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}}}