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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | 53x 56x 6x 2x 2x 2x 43x 1x 53x 53x 53x 53x 95x 95x 95x 95x 95x 95x 95x 95x 95x 95x 56x 6x 2x 48x 95x 56x 56x 95x 95x 95x 95x 1x 95x 95x | import { capitalize } from "lodash";
import NiceModal from "@ebay/nice-modal-react";
import {
CheckCircleIcon,
ClockIcon,
InfoIcon,
XCircleIcon,
} from "@phosphor-icons/react";
import { useAppTheme } from "@theme/v2/Provider";
import { cardIconsMap } from "features/MerchantPortal/ManageMoney/ManageMoneyTable.atoms";
import { useGetFeatureFlagValues } from "FeatureFlags/useGetFeatureFlagValues";
import { BIN_DETAILS_MODAL } from "modals/modal_names";
import { checkPortals } from "utils/routing";
import type { TransactionSummaryDetailsItem } from "../helpers/getTransactionLineItems";
type Props = {
data: any;
};
type MatchKind = "avs" | "cvv";
// TRA013 AC001.4 / AC005.1 — normalize the BE MSPMatchResult enum
// (match | mismatch | not_provided | unavailable) into a display label.
// Absent/unrecognized values → "Unknown" (clock indicator per the Figma design,
// which is authoritative here).
const normalizeMatchResult = (
raw: string | undefined | null,
kind: MatchKind,
): string => {
switch ((raw ?? "").toLowerCase()) {
case "match":
return "Match";
case "mismatch":
return "Mismatch";
case "not_provided":
// CVV can report "Not provided"; AVS has no such state (AC001.2).
return kind === "cvv" ? "Not provided" : "Unavailable";
case "unavailable":
return "Unavailable";
case "":
// Absent value → "Unknown" per the Figma design (clock indicator).
return "Unknown";
default:
return "Unknown";
}
};
// Educational tooltips shown on the AVS / CVV labels (Figma design).
const AVS_TOOLTIP =
"AVS verifies if the billing address matches the cardholder's record with the issuer";
const CVV_TOOLTIP =
"CVV is the 3-digit code used for validating card-not-present payments (4-digits for AMEX)";
const BANK_TOOLTIP =
"BIN data identifies the card's issuing bank and type, helpful in fraud detection";
export const useTransactionPaymentMethod = ({ data }: Props) => {
const { palette } = useAppTheme();
const { isAcquirerPortal, isEnterprisePortal } = checkPortals();
// Bank / AVS Match / CVV Match rows (TRA013) are only available on the
// acquirer and provider portals — never on the merchant portal.
const showVerificationDetails = isAcquirerPortal || isEnterprisePortal;
const { isPurchaseVerificationEnabled } = useGetFeatureFlagValues();
const isPurchaseVerificationOn = true; // Todo: use correct field from BE ex: merchant?.purchaseVerification
const showPurchaseConfirmation =
isPurchaseVerificationEnabled && isPurchaseVerificationOn;
const successColor = palette.primitive?.success?.[50];
const errorColor = palette.primitive?.error?.[50];
const neutralColor = palette.icon?.["icon-secondary"];
// Green check / red X / gray clock indicator next to the AVS / CVV result.
const matchStatusIcon = (label: string) => {
switch (label) {
case "Match":
return <CheckCircleIcon size={18} weight="fill" color={successColor} />;
case "Mismatch":
return <XCircleIcon size={18} weight="fill" color={errorColor} />;
default:
// Unknown / Unavailable / Not provided
return <ClockIcon size={18} color={neutralColor} />;
}
};
const buildMatchItem = (
label: string,
raw: string | undefined | null,
kind: MatchKind,
tooltip: string,
): TransactionSummaryDetailsItem => {
const value = normalizeMatchResult(raw, kind);
return {
label,
value,
icon: matchStatusIcon(value),
startIcon: <InfoIcon size={16} />,
tooltipMessage: tooltip,
};
};
// BIN metadata (TRA013 AC001 / AC002). Surfaced by the transactions_view join
// to payment_bin_infos on the card. For a purchase the customer's card is the
// source method; fall back to destination for money-out flows.
// GB-21572 — a refund/reversal performs its own auth request that runs no
// AVS/CVV verification, so its own mspStatusDetails is empty (Unavailable /
// Not provided). The BE surfaces the original settled transaction's results as
// originalTransactionMspStatusDetails (null for non-reversal transactions), so
// prefer those for the AVS/CVV rows and fall back to this transaction's own.
const verificationDetails =
data?.originalTransactionMspStatusDetails ?? data?.mspStatusDetails;
const bin =
data?.sourceMethod?.card?.bin ?? data?.destinationMethod?.card?.bin;
// The BE COALESCEs BIN columns to "", so an absent lookup still yields a bin
// object of empty strings — treat it as present only when it carries data.
const hasBin = Boolean(bin?.number || bin?.issuingBankName);
// "Bank" row (Figma): show the issuing bank name as a clickable link that opens
// the BIN modal — the BIN number itself lives inside the modal. If the bank name
// is missing fall back to the BIN number; with no BIN data show "Unavailable".
const bankValue = hasBin
? {
name: bin?.issuingBankName || bin?.number,
onClick: () =>
NiceModal.show(BIN_DETAILS_MODAL, {
bin,
cardBrand: data?.cardType,
}),
}
: "Unavailable";
const paymentMethodList: TransactionSummaryDetailsItem[] = [
{ label: "Name on Card", value: data?.cardHolderName },
{
label: "Card Brand",
value: capitalize(data?.cardType),
icon: cardIconsMap()[
data?.cardType as keyof ReturnType<typeof cardIconsMap>
],
},
{ label: "Card Number", value: `•••• ${data?.cardDigits}` },
{
label: "Billing Descriptor",
value: `GIV*${data?.billingDescriptor}`,
},
...(showPurchaseConfirmation
? [{ label: "Purchase Confirmation", value: "Email OTP" }]
: []),
// Bank / AVS Match / CVV Match are gated to the acquirer and provider
// portals only — they must not appear in the merchant portal.
...(showVerificationDetails
? [
// Bank row (AC001/AC002): clickable link opening the "Bank
// Identification Number" modal, with an educational tooltip.
{
label: "Bank",
value: bankValue,
startIcon: <InfoIcon size={16} />,
tooltipMessage: BANK_TOOLTIP,
testId: "transaction-bin-link",
},
// AVS / CVV match results with status icon + tooltip (AC001.4).
buildMatchItem(
"AVS Match",
verificationDetails?.msp_avs_status,
"avs",
AVS_TOOLTIP,
),
buildMatchItem(
"CVV Match",
verificationDetails?.msp_cvv_status,
"cvv",
CVV_TOOLTIP,
),
]
: []),
];
return {
paymentMethodList,
};
};
|