Skip to content

fix(adyen): send the shopper locale so redirect pages match the Drop-in language - #824

Open
acasazza wants to merge 3 commits into
fix/adyen-dropin-session-keyfrom
fix/adyen-shopper-locale-v5
Open

fix(adyen): send the shopper locale so redirect pages match the Drop-in language#824
acasazza wants to merge 3 commits into
fix/adyen-dropin-session-keyfrom
fix/adyen-shopper-locale-v5

Conversation

@acasazza

Copy link
Copy Markdown
Member

Targets fix/adyen-dropin-session-key (#818) rather than main, so it lands inside the work already open there.

The bug

Adyen uses two locales and only one of them was travelling:

  • locale in the Core configuration is client-side only — it selects the Drop-in's translation bundle.
  • the shopper locale in the payment request is what Adyen uses for the pages it renders, in particular the hosted page a redirect method sends the shopper to.

payment_request_data carried payment_method, return_url, origin, redirect_from_issuer_method, shopper_ip and browser_info, but no shopper locale, so Adyen fell back to the merchant account default or the country code. A Drop-in correctly rendered in English then handed over to a Klarna page in Italian for an IT market.

This is a port of ddfb9a7a, which fixed it on the v4 line and never reached v5.

What changed

shopper_locale is sent, derived from the same value that drives the Drop-in. Commerce Layer's language_code is a bare ISO 639-1 code, so getAdyenShopperLocale expands it to the language-REGION form Adyen expects (enen-US, itit-IT). A language it cannot expand confidently yields undefined and the key is omitted, preserving Adyen's own fallback rather than sending a locale it may reject. ca is expanded too, which ddfb9a7a missed.

AdyenPaymentConfig.shopperLocale overrides the derived value, camelCase as a component option. Unlike in ddfb9a7a it also moves the Drop-in's own locale: an integration that sets it would otherwise get a Klarna page in Italian behind a Drop-in in English, which is the original mismatch chosen on purpose. Nothing changes for an integration that does not set it.

Attributes are snake_case. ddfb9a7a sent shopperLocale, which is Adyen's name but not the one this payload takes — the API maps snake_case attributes onto Adyen's camelCase. shopperInteraction had the same problem and is now shopper_interaction; Ecommerce is Adyen's default for a request carrying browser_info, which is why nothing visibly broke while it was not arriving.

Two cleanups in their own commit: the locale prop defaulted to en_US, now a named DEFAULT_LOCALE in the language-REGION form Adyen documents (their client normalizes the underscore, the payment request does not); and AdyenGateway no longer casts order.language_code to Stripe's StripeElementLocale to feed an Adyen prop — ?? undefined launders the null without the lie.

Commits

ff111221 fix(adyen): send shopper_locale so redirect pages match the Drop-in language
c7ec0082 chore(adyen): spell the default locale the way Adyen documents it
58844107 fix(adyen): send shopper_interaction in snake_case like its neighbours

The port is revertible on its own.

Tests

specs/payment_source/AdyenPayment.shopperLocale.spec.tsx — 6 cases: derived from the order language, not from the country code, config override, the override moving the Drop-in with it, omission when the language cannot be expanded, fallback to the locale prop. specs/utils/adyenShopperLocale.spec.ts — 4 cases on the expansion, including en_US and PT_br normalization.

Full suite green: 1032 tests in react-components, 129 in core-components, 99 in react-hooks-components. pnpm typecheck unchanged from the baseline on this branch (22 pre-existing errors, none in the touched files).

Still unverified

The snake_case attribute name is not documented — the API reference lists only payment_method, origin, return_url and browser_info inside payment_request_data. If a redirect method still comes up in the wrong language, the attribute name is the first thing to check against a real order.

🤖 Generated with Claude Code

Alessandro Casazza and others added 3 commits August 27, 2026 19:48
…anguage

Port of ddfb9a7, which fixed this on the v4 line and never reached v5.

Adyen uses two locales and only one of them was travelling:

- `locale` in the Core configuration is client-side only — it selects the
  Drop-in's translation bundle.
- the shopper locale in the payment request is what Adyen uses for the pages it
  renders itself, in particular the hosted page a redirect method sends the
  shopper to.

`payment_request_data` carried `payment_method`, `return_url`, `origin`,
`redirect_from_issuer_method`, `shopper_ip` and `browser_info`, but no shopper
locale, so Adyen fell back to the merchant account default or the country code.
A Drop-in correctly rendered in English then handed over to a Klarna page in
Italian for an IT market.

`shopper_locale` is now sent, derived from the same value that drives the
Drop-in. Commerce Layer's `language_code` is a bare ISO 639-1 code, so
`getAdyenShopperLocale` expands it to the `language-REGION` form Adyen expects
(`en` -> `en-US`, `it` -> `it-IT`). When a language cannot be expanded
confidently it returns undefined and the field is omitted, preserving Adyen's
current fallback rather than sending a locale it may reject. `ca` is expanded
too, which ddfb9a7 missed.

The attribute is snake_case, like the other Commerce Layer attributes in that
payload — it is the API that maps them onto Adyen's camelCase names. ddfb9a7
sent `shopperLocale` instead, which is the name Adyen uses but not the one this
payload takes.

`AdyenPaymentConfig.shopperLocale` overrides the derived value, camelCase as a
component option. Unlike in ddfb9a7 it also moves the Drop-in's own `locale`:
an integration that sets it would otherwise get a Klarna page in Italian behind
a Drop-in in English, which is the original mismatch chosen on purpose. Nothing
changes for an integration that does not set it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things the shopperLocale work walked past.

The `locale` prop defaulted to `en_US`. Adyen's client normalizes that — it
replaces the underscore and then matches against its supported list — so it
worked, but the payment request has no such normalizer, and the same string in
both places would not have. It is now a named `DEFAULT_LOCALE` in the
`language-REGION` form, which is also Adyen's own fallback value.

`AdyenGateway` cast `order.language_code` to `StripeElementLocale`, importing a
Stripe type to feed an Adyen prop. It compiled only because that prop is
`string`. `language_code` is `string | null` and the prop is
`string | undefined`, so the cast was laundering the null: `?? undefined` does
that without claiming the value is something it is not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`payment_request_data` is a Commerce Layer payload and the API maps its
snake_case keys onto Adyen's camelCase names. `shopperInteraction` was written
with Adyen's own spelling, so it went into the payload under a name that side
does not take — the same mistake the shopper locale nearly shipped with.

`Ecommerce` is Adyen's own default for this field, so a payment that has been
working was very likely not relying on it arriving. It arrives now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@acasazza acasazza added bug Something isn't working components Components package labels Aug 27, 2026
@acasazza acasazza self-assigned this Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working components Components package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant