Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Headless sales channel - webhooks cannot be used #401

Open
AndreasA opened this issue Jul 20, 2023 · 6 comments
Open

Headless sales channel - webhooks cannot be used #401

AndreasA opened this issue Jul 20, 2023 · 6 comments
Assignees

Comments

@AndreasA
Copy link
Contributor

AndreasA commented Jul 20, 2023

Describe the bug
The webhook notification receiver currently requires Storefront sales channels as its route scope is storefront.

It would be great if it could also be used for headless sales channels.

Necessary changes:

  • RouteScope chnages:
    • either use an API integration with corresponding ACL role.
    • create a custom route scope for adyen, so the old path can be used and keep the current authentication methods.
      • this might be the better solution as you can add the adyen routescope and storefront to ensure old webhooks will continue to work.
  • Somehow add an information regarding the sales channel to the webhook call, e.g. sales channel ID query parameter or header.
    • adding it optionally to the path might also be a solution, that way headless integrations can use the ID there, but for existing ones it will still work through the domain paths.

It will work for storefront sales channels that are also used headless, but for headless only sales channels it will fail.

Versions
Shopware version: 6.5.3.3
Plugin version: 3.12.1

See https://github.com/Adyen/adyen-shopware6/blob/3.12.1/src/Storefront/Controller/NotificationReceiverController.php

@AndreasA
Copy link
Contributor Author

AndreasA commented Jul 27, 2023

@peterojo I think this might actually be quite simple to fix, by e.g. creating a store api route that has auth required set to false, and the sales channel ID as path parameter (potentially optional parameter) and the sales channel ID passed to the notfication service, and then calls the notfiication service. that one should also work for storefront sales channels but using different routes could even be by design.

I could create a corresponding PR.

I would suggest something like /store-api/adyen/notification/<sales-channel-id>

@candemiralp candemiralp self-assigned this Feb 13, 2024
@AndreasA
Copy link
Contributor Author

any news regarding this?

@adyen-integrations-support

Hello,

Thank you for reaching out. This is just a quick note to inform you that we received your ticket.

We will take this issue into consideration as soon as possible.

Best regards

@adyen-integrations-support

Hi,

Please note that we are actively working on addressing this matter. Once we have more updates or a resolution, we will inform you.

Thank you for your patience and understanding.

Best regards

@adyen-integrations-support

Hi @AndreasA ,

The Adyen plugin uses the sw-sales-channel-id request attribute set by Shopware framework to determine the sales channel for the webhooks requests. Shopware sets the sw-sales-channel-id request based on the request URL by matching the configured domains of a sales channel with the request domain.

Once the sales channel is determined from the webhooks request, the plugin will perform HMAC and username/password authentication based on plugin configuration values. Please note that you can set up different configuration values for each sales channel.

Please note that Adyen plugin exposes store API endpoints to enable building a custom front end for your headless Shopware 6 integration using either Adyen's pre-built UI solutions or your own UI. Your front end connects to your existing back end that uses Shopware's Store API endpoints (more details can be found here).

Based on the above, the plugin behavior behind the NotificationReceiverController is in line with its purpose of handling webhooks from Adyen.

This is a duplicate of this ticket that we already responded: #261

We will close that ticket so please let us know if you need further assistance or if there’s anything else we can do to support you on this ticket.

Best regards

@AndreasA
Copy link
Contributor Author

AndreasA commented Jan 30, 2025

Hi @teodoratimoti that is not entirely correct.

If one reates a headless sales channel that channel can only access store-api calls and some special routes like finalize payment (as that route is not a Storefront route but available in general).

Furthermore, the domain of the sales channel is not used to determine the sw-sales-channel-id for store api routes, only for Strorefront routes.

See also:
https://github.com/shopware/shopware/blob/trunk/src/Storefront/Framework/Routing/DomainLoader.php#L61
and https://github.com/shopware/shopware/blob/trunk/src/Storefront/Framework/Routing/RequestTransformer.php#L259
and https://github.com/shopware/shopware/blob/trunk/src/Storefront/Framework/Routing/RequestTransformer.php#L109
and https://github.com/shopware/shopware/blob/trunk/src/Storefront/Framework/Routing/RequestTransformer.php#L174

Also Storefront is a separate composer dependency that might not even be installed on headless only installations.

The NotificationReceiverController is a Storefront only controller, see https://github.com/Adyen/adyen-shopware6/blob/develop/src/Storefront/Controller/NotificationReceiverController.php#L33

Therefore, it cannot be accessed with a headless sales channel as such a sales channel only exposes the store-api routes. Furthermore, even if the routescope is changed, it will not set the sales-channel-id correctly (at least not through the path alone).

You would need a second route that e.g. uses the sales channel id as part of its route or similar.

Shopware usually provides a Storefront controller that calls a headless route - or in your case, you would just need a second controller that calls the notfication process service.
However, you can check: https://github.com/shopware/shopware/blob/trunk/src/Storefront/Controller/ContextController.php#L44
and: https://github.com/shopware/shopware/blob/trunk/src/Core/System/SalesChannel/SalesChannel/ContextSwitchRoute.php#L52

However, in your scenario you would need to also mark the route as being callable without the access-key which can be easily done by setting auth_required to false

see https://github.com/shopware/shopware/blob/trunk/src/Core/Framework/Api/EventListener/Authentication/SalesChannelAuthenticationListener.php#L63
and there is also a fixture route for a test case that does something similar:
https://github.com/shopware/shopware/blob/trunk/tests/integration/Core/Content/Seo/SalesChannel/FixturesPhp/StoreApiSeoResolverTestRoute.php
(without the hmac etc. security part).

otherwise the sw-access-key header would have to be added by the webhook call: https://shopware.stoplight.io/docs/store-api/authentication to determine the correct context/sales channel id.

So e.g. a store api controller like this would probably work:

<?php declare(strict_types=1);
/**
 *                       ######
 *                       ######
 * ############    ####( ######  #####. ######  ############   ############
 * #############  #####( ######  #####. ######  #############  #############
 *        ######  #####( ######  #####. ######  #####  ######  #####  ######
 * ###### ######  #####( ######  #####. ######  #####  #####   #####  ######
 * ###### ######  #####( ######  #####. ######  #####          #####  ######
 * #############  #############  #############  #############  #####  ######
 *  ############   ############  #############   ############  #####  ######
 *                                      ######
 *                               #############
 *                               ############
 *
 * Adyen Payment Module
 *
 * Copyright (c) 2020 Adyen B.V.
 * This file is open source and available under the MIT license.
 * See the LICENSE file for more info.
 *
 * Author: Adyen <[email protected]>
 */

namespace Adyen\Controller\StoreApi;

use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\Request;
use Adyen\Shopware\Service\NotificationReceiverService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;

#[Route(defaults: ['_routeScope' => ['store-api']])]
class NotificationReceiverController extends StorefrontController
{
    /** @var NotificationReceiverService */
    private NotificationReceiverService $notificationReceiverService;

    /**
     * NotificationReceiverController constructor.
     *
     * @param NotificationReceiverService $notificationReceiverService
     */
    public function __construct(NotificationReceiverService $notificationReceiverService)
    {
        $this->notificationReceiverService = $notificationReceiverService;
    }

    #[Route(
        '/store-api/adyen/notification/{salesChannelId}',
        name: 'store-api.adyen.notification',
        defaults: ['csrf_protected' => false],
        methods: ['POST']
    )]
    public function execute(Request $request, string $salesChannelId): JsonResponse
    {
        // TODO: Verify sales channel id iis not empty and the sales channel exists and is active.
        //             If not, probably throw an unauthorized excpetion or similar.
        //             All other authentication checks should be done in the notificationREceiverService.
        // TODO 2: Probably better to pass the sales channel id to the notificationREceiverService directly
        //                instead of modifying the request headers.
        $request->headers->set('sw-sales-channel-id', $salesChannelId);

        return $this->notificationReceiverService->process($request);
    }
}

@teodoratimoti teodoratimoti reopened this Jan 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants