Versions Compared

Key

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

...

Whitelabel:

  • Task 1: Create a new feature flag

  • Task 2: Create a new payment method option

...

Components tree architecture

...

Tasks breakdown

The solution was based on the Sodexo 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_CREDIT_CARD_AT_HOME_PAYCOMET

Task X: Add the new payment method in the payment state and structure

  • 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
// 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,
};
  • 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 {
  enableNewMethod: boolean;
}

Adjust the getPaymentMethodsState:

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

  • Adjust the payment hook

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

Update cash account variable:

Code Block
languagetypescript
const cashAccount: IPaymentMethod = {
  // ... rest of the code
  newMethod: null,
};

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

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

  • 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 [isPaycometNewMethodSelected, setIsPaycometNewMethodSelected] = useState('');

Update the handlePaymentMethodSelected adding the new method:

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

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
      setIsPaycometNewMethodSelected('');
    }
  }, [setCheckoutPaymentMethodId, checkoutPaymentMethodId]);

Update the placeOrder function to deal with the new method:

  • Option 1:

    • Adjust the if condition for paycomet processor to deal with the new method:

      Code Block
      languagetypescript
      } else if (payment.isPaycomet) {
              // ... rest of the code
              if (payCometValues.isPaypal) {
                // ... rest of the code
              } else {
                commitInput = {
                  creditType: payCometValues.cardType,
                  order,
                  payment: {
                    // ... rest of the code
                    // adjust this condition  below to deal with Sodexo and also with our new method
                    paymentMethodBrand: isPaycometSodexoMethodSelected
                      ? PaymentMethodBrand.SODEXO
                      : undefined,
                    // ... rest of the code
                  },
                  skipCoolingPeriod: true,
                };
              }
            }
        // ... rest of the code

  • Option 2:

Task 2: 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
  newPaymentMethod?: 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: 'payWithNewMethod' })}</MethodType>;
};

export default NewPaymentMethod;

...

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.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 X: 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 !== NEW_PAYMENT_METHOD_PLACEHOLDER.accountIdentifier &&
  );
}