Summary/Problem
The merchant-return route (used when a merchant manually adds a payment via Add payment → Add and the gateway redirects back, e.g. Authorize.net Accept Hosted) is defined as an admin path:
commerce_payment.order.merchant_return:
path: '/admin/commerce/orders/{commerce_order}/payment-gateways/{commerce_payment_gateway}/merchant-return' defaults:
_controller: '\Drupal\commerce_payment\Controller\OrderPaymentController::merchantReturn'
options:
parameters:
commerce_order:
type: 'entity:commerce_order'
commerce_payment_gateway:
type: 'entity:commerce_payment_gateway'
Because it's under /admin and commerce_payment_gateway is a config entity, Drupal core's
\Drupal\Core\ParamConverter\AdminPathConfigEntityConverter takes over conversion instead of the default
EntityConverter, and loads the entity override-free ($storage->loadOverrideFree($value)). This is intentional core
behavior — see core.api.php, with_config_overrides — so that admin edit forms show/save the un-overridden config
rather than accidentally persisting an environment override back into config.
The problem: merchantReturn() isn't an edit form. It's the controller that makes the actual server-to-server call
back to the payment gateway ($payment_gateway_plugin->onReturn($order, $request)) to finalize the transaction. A very
common pattern for keeping API credentials out of exported config (recommended in several places, including
commerce_authnet's own maintainers) is to inject them via a $config[] override in settings.php:
$config['commerce_payment.commerce_payment_gateway.accept_hosted']['configuration']['api_login'] = $api_login;
$config['commerce_payment.commerce_payment_gateway.accept_hosted']['configuration']['transaction_key'] =
$transaction_key;
Because this route's commerce_payment_gateway parameter is loaded override-free, that override is silently discarded
on this specific request, and the gateway plugin authenticates with whatever placeholder/blank value is in the
exported config/sync YAML instead of the real environment credentials. For us this manifested as Authorize.net
rejecting the follow-up API call with E00007: User authentication failed due to invalid authentication values — even
though the card had already been charged successfully via the hosted payment page (which does correctly resolve the
override, since it isn't loaded through this route).
Notably, commerce_payment.notify (/payment/notify/{commerce_payment_gateway}) does not have this problem, since it
isn't an admin path — AdminPathConfigEntityConverter only applies to admin routes, so the standard override-aware
converter is used there. That's a useful point of comparison: merchant_return is arguably the odd one out, since itneeds live credentials for the same reason notify does.
Steps to reproduce
1. Configure an offsite gateway (e.g. Authorize.net Accept Hosted) with a $config[] override in settings.php for the credentials, matching the placeholder/blank value committed to config/sync.
2. As an admin, go to an order → Payments → Add payment, select the offsite gateway, and submit a card.
3. The gateway redirects back to /admin/commerce/orders/{order}/payment-gateways/{gateway}/merchant-return.
4. The charge succeeds on the gateway's side, but the plugin's follow-up API call (creating the payment/payment method locally) authenticates with the un-overridden placeholder credentials and fails.
Proposed resolution
Add with_config_overrides: TRUE to the commerce_payment_gateway parameter on commerce_payment.order.merchant_return:
commerce_payment_gateway:
type: 'entity:commerce_payment_gateway'
with_config_overrides: TRUE
This restores the standard override-aware EntityConverter for just this route, without affecting the payment gateway edit/add forms (which correctly keep loading override-free).
Remaining tasks
- Confirm whether any other commerce_payment (or commerce_payment-adjacent, e.g. commerce_authnet's own routes if any) admin routes carrying a commerce_payment_gateway parameter have the same issue.
Issue fork commerce-3623770
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
vmarchukComment #6
jsacksick commentedMerged!