Skip to content

FrmCurrencyHelper::prepare_price() has the same locale-mismatched-separator truncation bug as #3379 #3381

Description

@vivi-the-going-merry

Follow-up from #3379 / PR #3380, flagged during that fix's self-review.

classes/helpers/FrmCurrencyHelper.php's prepare_price()/maybe_use_decimal() (lines ~95-134) implements the same "strip thousand separator, then blindly swap decimal separator to ." algorithm that FrmTransLiteActionsController::prepare_amount() had, and has the identical root-cause bug: it never checks whether the amount string already contains the currency's other configured separator character in a conflicting role.

Repro (traced, not yet turned into a failing test in that PR since it's a different consumer): a EUR-configured currency (decimal_separator=',', thousand_separator='.') fed a US-style amount "1,030.21":

  • maybe_use_decimal() sees thousand_separator === '.', finds one . with a 2-digit tail, treats it as a misplaced decimal point, and rewrites it: "1,030,21".
  • prepare_price() then does str_replace(',', '.', ...) on every comma: "1.030.21".
  • PHP's (float) cast on "1.030.21" stops parsing at the second ., silently returning 1.03 — the same ~1000x truncation as Stripe/Trans amount parser silently truncates on locale-mismatched separators #3379.

Consumer: FrmFieldTotal (classes/models/fields/FrmFieldTotal.php:108,116,131) calls FrmCurrencyHelper::prepare_price() for Total field price calculations — so this affects Total field math, not payment charges directly, but it's the same class of silent-wrong-money bug.

Suggested fix: apply the same approach PR #3380 used in FrmTransLiteActionsController — a single find_decimal_position()-style helper that detects the real decimal separator from the string itself (rightmost separator wins when both appear) instead of trusting the currency's configured separators, then splits/strips rather than blanket-replacing by character (a repeated occurrence of the resolved decimal character has to not collide into a second decimal point either — see PR #3380's second commit for why the naive single-swap version isn't enough).

Given the shared logic between the two classes, this might also be a good opportunity to extract one shared helper instead of maintaining two parallel implementations of the same parsing algorithm — noted by the reuse pass on #3380's self-review, but left out of that PR to keep its diff scoped to the reported issue.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions