All files / src/features/Settlements/ReconciliationHub/components HubProcessorDropdown.tsx

96.42% Statements 27/28
100% Branches 25/25
85.71% Functions 6/7
95.65% Lines 22/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104                            3x         3x               3x 62x 62x 62x                 62x           42x 28x 48x   17x 30x   17x 9x 9x       8x 7x                       62x   46x       34x       62x               62x                            
import { processorMap } from "@features/Merchants/MerchantSidePanel/constants";
import { useGetProcessors } from "@hooks/acquirer-api/merchants/useGetProcessors";
import { useGetCurrentMerchantId } from "@hooks/common";
import { useEffect, useMemo } from "react";
import { useAcquirerDefaultProcessor } from "../hooks/useAcquirerDefaultProcessor";
import { UNRESOLVED_PROCESSOR } from "../useHubFilters";
import HubDropdown, { HubDropdownOption } from "./HubDropdown";
 
interface Props {
  value: string;
  onChange: (processorName: string) => void;
  disabled?: boolean;
}
 
const ALL_PROCESSORS: HubDropdownOption = {
  value: "all",
  label: "All Processors",
};
 
const RESOLVING_LABEL = "Processor";
 
/**
 * Controlled processor selector for the Reconciliation Hub. Reuses
 * `useGetProcessors` + `processorMap` (the same source as the shared
 * `ProcessorDropdownFilter`) but drives the hub's local filter state, and owns
 * resolving the hub's initial processor — see the effect below.
 */
const HubProcessorDropdown = ({ value, onChange, disabled }: Props) => {
  const { merchantId } = useGetCurrentMerchantId();
  const { data, isLoading } = useGetProcessors(merchantId);
  const { defaultProcessorName, isResolving } = useAcquirerDefaultProcessor();
 
  // Resolves the hub's initial processor to the acquirer's own default
  // (GB-21646) and corrects a value the acquirer doesn't have. A specific
  // processor is only ever auto-selected when it is known to be the acquirer's
  // default: the catalogue is global and arbitrarily ordered, so picking from
  // it would just as easily land a non-Worldpay acquirer on Worldpay — the very
  // empty hub this fixes. Everything else resolves to "all", which is both
  // truthful and populated (the BE materialises parent rows at processor_id 0).
  useEffect(() => {
    // `!merchantId` matters on its own: both queries are `enabled: !!id`, so
    // before getAccounts() lands they sit idle — undefined data, isLoading
    // false — and the "all" fallback below would latch before either had a
    // chance to run, permanently (an "all" value short-circuits every later
    // resolution).
    if (!merchantId || isLoading || isResolving) return;
    if (value === ALL_PROCESSORS.value) return;
    if (data?.some((processor) => processor.name === value)) return;
 
    const defaultOption = data?.find(
      (processor) => processor.name === defaultProcessorName,
    );
    if (defaultOption) {
      onChange(defaultOption.name);
      return;
    }
    // An unusable catalogue only ever rescopes the seed — a failed background
    // refetch must not pull an existing selection out from under the user.
    if (data?.length || value === UNRESOLVED_PROCESSOR) {
      onChange(ALL_PROCESSORS.value);
    }
  }, [
    merchantId,
    data,
    isLoading,
    value,
    defaultProcessorName,
    isResolving,
    onChange,
  ]);
 
  const options = useMemo<HubDropdownOption[]>(() => {
    const processorOptions =
      data?.map((processor) => ({
        value: processor.name,
        label: processorMap[processor.name] ?? processor.name,
      })) ?? [];
    return [ALL_PROCESSORS, ...processorOptions];
  }, [data]);
 
  const selectedLabel =
    value === UNRESOLVED_PROCESSOR
      ? RESOLVING_LABEL
      : value === ALL_PROCESSORS.value
      ? ALL_PROCESSORS.label
      : // Fall back to the raw processor key (not a hardcoded "Worldpay") when a
        // processor isn't in processorMap.
        processorMap[value as keyof typeof processorMap] ?? value;
 
  return (
    <HubDropdown
      dataTestId="hub-processor-dropdown"
      selectedValue={value}
      selectedLabel={selectedLabel}
      options={options}
      isLoading={isLoading}
      onSelect={(option) => onChange(option.value)}
      disabled={disabled}
    />
  );
};
 
export default HubProcessorDropdown;