| @@ -94,31 +94,62 @@ | ||
| 94 | 94 | "before" |
| 95 | 95 | ); |
| 96 | 96 | } |
| 97 | 97 | |
| 98 | -function readAdminNumberFormatConfig(): { | |
| 98 | +function readYatraNumberFormatConfig(): { | |
| 99 | 99 | decimalPlaces: number; |
| 100 | 100 | thousandSeparator: string; |
| 101 | 101 | decimalSeparator: string; |
| 102 | 102 | } { |
| 103 | + const fallback = { | |
| 104 | + decimalPlaces: 2, | |
| 105 | + thousandSeparator: ",", | |
| 106 | + decimalSeparator: ".", | |
| 107 | + }; | |
| 103 | 108 | if (typeof window === "undefined") { |
| 104 | - return { decimalPlaces: 2, thousandSeparator: ",", decimalSeparator: "." }; | |
| 109 | + return fallback; | |
| 105 | 110 | } |
| 106 | - const a = ((window as AdminWindow).yatraAdmin || {}) as Record< | |
| 107 | - string, | |
| 108 | - unknown | |
| 109 | - >; | |
| 110 | - const decimals = Number(a.decimalPlaces ?? a.currency_decimals ?? 2) || 2; | |
| 111 | + // Read from the SAME set of globals as the currency position, not just | |
| 112 | + // yatraAdmin. On public pages (customer account, checkout, single trip) | |
| 113 | + // window.yatraAdmin is absent, so reading separators only from it made the | |
| 114 | + // decimal/thousand separators silently fall back to defaults ("." / ",") | |
| 115 | + // while the position — read from these public globals — was correct. That is | |
| 116 | + // the "decimal separator ignored in payment amounts" bug on the storefront. | |
| 117 | + const w = window as AdminWindow; | |
| 118 | + const sources: Array<Record<string, unknown> | undefined> = [ | |
| 119 | + w.yatraAdmin as Record<string, unknown> | undefined, | |
| 120 | + w.yatraBookingData as Record<string, unknown> | undefined, | |
| 121 | + w.yatraTripData as Record<string, unknown> | undefined, | |
| 122 | + w.yatraAccountPage as Record<string, unknown> | undefined, | |
| 123 | + ]; | |
| 124 | + const pickString = (field: string): string | undefined => { | |
| 125 | + for (const o of sources) { | |
| 126 | + const v = o?.[field]; | |
| 127 | + if (typeof v === "string" && v !== "") { | |
| 128 | + return v; | |
| 129 | + } | |
| 130 | + } | |
| 131 | + return undefined; | |
| 132 | + }; | |
| 133 | + const pickNumber = (...fields: string[]): number | undefined => { | |
| 134 | + for (const o of sources) { | |
| 135 | + if (!o) { | |
| 136 | + continue; | |
| 137 | + } | |
| 138 | + for (const field of fields) { | |
| 139 | + const v = o[field]; | |
| 140 | + if (v !== undefined && v !== null && v !== "" && !Number.isNaN(Number(v))) { | |
| 141 | + return Number(v); | |
| 142 | + } | |
| 143 | + } | |
| 144 | + } | |
| 145 | + return undefined; | |
| 146 | + }; | |
| 147 | + const decimals = pickNumber("decimalPlaces", "currency_decimals") ?? 2; | |
| 111 | 148 | return { |
| 112 | 149 | decimalPlaces: Math.max(0, Math.min(4, decimals)), |
| 113 | - thousandSeparator: | |
| 114 | - typeof a.thousandSeparator === "string" && a.thousandSeparator !== "" | |
| 115 | - ? (a.thousandSeparator as string) | |
| 116 | - : ",", | |
| 117 | - decimalSeparator: | |
| 118 | - typeof a.decimalSeparator === "string" && a.decimalSeparator !== "" | |
| 119 | - ? (a.decimalSeparator as string) | |
| 120 | - : ".", | |
| 150 | + thousandSeparator: pickString("thousandSeparator") ?? ",", | |
| 151 | + decimalSeparator: pickString("decimalSeparator") ?? ".", | |
| 121 | 152 | }; |
| 122 | 153 | } |
| 123 | 154 | |
| 124 | 155 | /** |
| @@ -137,9 +168,9 @@ | ||
| 137 | 168 | return __("Contact for pricing", "yatra"); |
| 138 | 169 | } |
| 139 | 170 | |
| 140 | 171 | const { decimalPlaces, thousandSeparator, decimalSeparator } = |
| 141 | - readAdminNumberFormatConfig(); | |
| 172 | + readYatraNumberFormatConfig(); | |
| 142 | 173 | const currencyData = getCurrency(currencyCode); |
| 143 | 174 | const decimals = |
| 144 | 175 | currencyData?.decimalDigits !== undefined |
| 145 | 176 | ? Math.max(0, Math.min(4, currencyData.decimalDigits)) |
| @@ -144,9 +175,13 @@ | ||
| 144 | 175 | currencyData?.decimalDigits !== undefined |
| 145 | 176 | ? Math.max(0, Math.min(4, currencyData.decimalDigits)) |
| 146 | 177 | : decimalPlaces; |
| 147 | 178 | |
| 148 | - const formattedCore = new Intl.NumberFormat(undefined, { | |
| 179 | + // Format with a FIXED "en-US" base (grouping ",", decimal ".") so the | |
| 180 | + // replace-with-configured-separators step below is deterministic. Using the | |
| 181 | + // viewer's locale here (undefined) would already yield locale separators and | |
| 182 | + // the naive comma/dot replacement would swap them for e.g. German browsers. | |
| 183 | + const formattedCore = new Intl.NumberFormat("en-US", { | |
| 149 | 184 | minimumFractionDigits: decimals, |
| 150 | 185 | maximumFractionDigits: decimals, |
| 151 | 186 | }) |
| 152 | 187 | .format(num) |