Skip to content

fix(payment): round to currency precision regardless of server locale - #17452

Merged
kodiakhq[bot] merged 2 commits into
developfrom
fix/payment-currency-precision-locale
Oct 23, 2026
Merged

kodiakhq[bot] merged 2 commits into
developfrom
fix/payment-currency-precision-locale

Conversation

@medusa-os-bot

@medusa-os-bot medusa-os-bot Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Summary

What — The Payment Module now works out a currency's rounding precision without depending on the server's locale. This fixes rounding for zero-decimal currencies and for servers running under locales that use a comma as the decimal separator.

Why — roundToCurrencyPrecision counted decimals by formatting a number with Intl.NumberFormat in the default locale and splitting the result on ".". That breaks in two cases:

  • For JPY (and other zero-decimal currencies) there is no "." at all.
  • Under locales like de-DE the separator is ",".

In both cases the split threw, the catch kept full precision, and capture checks compared unrounded amounts. Valid captures were then rejected with "You cannot capture more than the authorized amount...", and full-capture detection was also affected. Only servers running en-US-style locales with two-decimal currencies worked.

How — packages/modules/payment/src/services/payment-module.ts:177 now reads resolvedOptions().maximumFractionDigits from the formatter. That value comes from the currency's data, not the formatted string, so it doesn't depend on the locale. It gives 0 for JPY and 2 for EUR/USD under every locale. Unknown currencies still throw and keep full precision, as before.

Testing — I added two tests to packages/modules/payment/integration-tests/__tests__/services/payment-module/index.spec.ts:

  • capturing 2000 JPY against an authorization of 1999.6 JPY
  • capturing 200.13 EUR against an authorization of 200.129 EUR with Intl.NumberFormat forced to de-DE

Both failed before the fix with the "cannot capture more than the authorized amount" error, and both pass after it. The full spec (68 tests) and the package unit tests pass.


Checklist

  • I have added a changeset for this PR
  • The changes are covered by relevant tests
  • I have verified the code works as intended locally
  • I have linked the related issue(s) if applicable

Additional Context

The module integration runner loads the payment module from dist, and the package couldn't be rebuilt in this environment. So I ran the spec against src by temporarily passing resolve: <package>/src to moduleIntegrationTestRunner, then removed that change. CI builds before running integration tests, so the committed spec doesn't need it.

Fixes #16974

Opened automatically by the Fix Bug workflow. Review carefully before merging.

@medusa-os-bot
medusa-os-bot Bot requested a review from a team as a code owner October 22, 2026 16:31
@changeset-bot

changeset-bot Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

ðŸĶ‹ Changeset detected

Latest commit: 41552a3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 83 packages
Name Type
@medusajs/payment Patch
@medusajs/medusa Patch
@medusajs/test-utils Patch
@medusajs/loyalty-plugin Patch
@medusajs/medusa-oas-cli Patch
integration-tests-http Patch
@medusajs/analytics Patch
@medusajs/api-key Patch
@medusajs/auth Patch
@medusajs/caching Patch
@medusajs/cart Patch
@medusajs/currency Patch
@medusajs/customer Patch
@medusajs/file Patch
@medusajs/fulfillment Patch
@medusajs/index Patch
@medusajs/inventory Patch
@medusajs/link-modules Patch
@medusajs/locking Patch
@medusajs/notification Patch
@medusajs/order Patch
@medusajs/pricing Patch
@medusajs/product Patch
@medusajs/promotion Patch
@medusajs/rbac Patch
@medusajs/region Patch
@medusajs/sales-channel Patch
@medusajs/search Patch
@medusajs/settings Patch
@medusajs/stock-location Patch
@medusajs/store Patch
@medusajs/tax Patch
@medusajs/translation Patch
@medusajs/user Patch
@medusajs/workflow-engine-inmemory Patch
@medusajs/workflow-engine-redis Patch
@medusajs/search-postgres Patch
@medusajs/draft-order Patch
@medusajs/oas-github-ci Patch
@medusajs/cache-inmemory Patch
@medusajs/cache-redis Patch
@medusajs/event-bus-local Patch
@medusajs/event-bus-redis Patch
@medusajs/analytics-local Patch
@medusajs/analytics-posthog Patch
@medusajs/auth-emailpass Patch
@medusajs/auth-github Patch
@medusajs/auth-google Patch
@medusajs/auth-oidc Patch
@medusajs/caching-redis Patch
@medusajs/file-local Patch
@medusajs/file-s3 Patch
@medusajs/fulfillment-manual Patch
@medusajs/locking-postgres Patch
@medusajs/locking-redis Patch
@medusajs/notification-local Patch
@medusajs/notification-sendgrid Patch
@medusajs/payment-stripe Patch
@medusajs/core-flows Patch
@medusajs/framework Patch
@medusajs/instantsearch-adapter Patch
@medusajs/js-sdk Patch
@medusajs/modules-sdk Patch
@medusajs/orchestration Patch
@medusajs/query Patch
@medusajs/types Patch
@medusajs/utils Patch
@medusajs/workflows-sdk Patch
create-medusa-app Patch
@medusajs/http-types-generator Patch
@medusajs/cli Patch
@medusajs/deps Patch
@medusajs/eslint-plugin Patch
@medusajs/telemetry Patch
@medusajs/admin-bundler Patch
@medusajs/admin-sdk Patch
@medusajs/admin-shared Patch
@medusajs/admin-vite-plugin Patch
@medusajs/dashboard Patch
@medusajs/icons Patch
@medusajs/toolbox Patch
@medusajs/ui-preset Patch
@medusajs/ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@medusa-os-bot

medusa-os-bot Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the contribution! Initial automated review looks good.

Verdict: Looks good for initial approval.

Replaces the locale-dependent Intl.NumberFormat.format().split(".") approach in roundToCurrencyPrecision with resolvedOptions().maximumFractionDigits, which reads each currency's decimal count directly from the JS engine — correctly handling zero-decimal currencies (JPY) and servers running non-en locales.

Checked: security (no user-controlled input in the changed path), performance (no query or loop changes), correctness, conventions, changeset (patch bump, correct format), tests (two integration tests covering JPY and the de-DE locale mock).

Triggered by: new PR opened

@medusa-os-bot

medusa-os-bot Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the contribution! Initial automated review looks good.

Verdict: Looks good for a final review by the team.

Replaces the locale-sensitive Intl.NumberFormat.format().split(".") pattern in roundToCurrencyPrecision with resolvedOptions().maximumFractionDigits, which reads each currency's decimal count from the ICU data regardless of server locale. This correctly returns 0 for JPY and 2 for EUR/USD under every locale, fixing failed captures for zero-decimal currencies and non-en servers.

Checked: security (no user-controlled input in this path), performance (synchronous calculation, no query changes), correctness, conventions, changeset (patch bump for @medusajs/payment), tests (unit tests across 4 locales × 4 currencies; 4 integration tests covering JPY and de-DE locale mocking).

Triggered by: new commit pushed

@kodiakhq
kodiakhq Bot merged commit b07a970 into develop Oct 23, 2026
38 checks passed
@kodiakhq
kodiakhq Bot deleted the fix/payment-currency-precision-locale branch October 23, 2026 08:53
@github-actions github-actions Bot mentioned this pull request Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: roundToCurrencyPrecision breaks under non-en locales and zero-decimal currencies

1 participant