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

Command icon 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

vmarchuk created an issue. See original summary.

vmarchuk’s picture

Assigned: vmarchuk » Unassigned
Status: Active » Needs review

jsacksick made their first commit to this issue’s fork.

  • jsacksick committed a139f92d on 3.x authored by vmarchuk
    fix: #3623770 commerce_payment.order.merchant_return loses config...
jsacksick’s picture

Status: Needs review » Fixed

Merged!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.