Skip to content

Meta Checkout URL Implementation #39667

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

Open
wants to merge 19 commits into
base: 2.4-develop
Choose a base branch
from

Conversation

sol-loup
Copy link

@sol-loup sol-loup commented Feb 27, 2025

Meta Checkout URL Implementation

This PR adds support for Meta's checkout URL specification, allowing direct linking to the checkout page with pre-populated cart items and coupon codes.

What's Changed

  • Added AddToCartLinkV1 controller that processes URL parameters to populate the cart
  • Added layout XML file to render the checkout page with consistent user experience
  • Implemented support for multiple products and coupon codes via URL parameters

How It Works

The controller accepts URL parameters in this format:
/checkout/cart/addtocartlinkv1?products=123:2,456:1&coupon=SAVE10

Screenshot 2025-02-27 at 2 45 27 PM

This will:

  1. Clear the existing cart (per Meta requirements)
  2. Add 2 units of product Performance problem & memory leak in Mage_Index_Model_Process #123 and 1 unit of product Bogus error message if mcrypt extension is not installed. #456
  3. Apply the "SAVE10" coupon code
  4. Display the checkout page

The implementation handles error cases gracefully, showing appropriate messages for invalid products or coupons.

Resolved issues:

  1. resolves [Issue] Meta Checkout URL Implementation #39700: Meta Checkout URL Implementation

Copy link

m2-assistant bot commented Feb 27, 2025

Hi @sol-loup. Thank you for your contribution!
Here are some useful tips on how you can test your changes using Magento test environment.
❗ Automated tests can be triggered manually with an appropriate comment:

  • @magento run all tests - run or re-run all required tests against the PR changes
  • @magento run <test-build(s)> - run or re-run specific test build(s)
    For example: @magento run Unit Tests

<test-build(s)> is a comma-separated list of build names.

Allowed build names are:
  1. Database Compare
  2. Functional Tests CE
  3. Functional Tests EE
  4. Functional Tests B2B
  5. Integration Tests
  6. Magento Health Index
  7. Sample Data Tests CE
  8. Sample Data Tests EE
  9. Sample Data Tests B2B
  10. Static Tests
  11. Unit Tests
  12. WebAPI Tests
  13. Semantic Version Checker

You can find more information about the builds here
ℹ️ Run only required test builds during development. Run all test builds before sending your pull request for review.


For more details, review the Code Contributions documentation.
Join Magento Community Engineering Slack and ask your questions in #github channel.

@engcom-Hotel
Copy link
Contributor

@magento run all tests

@engcom-Hotel
Copy link
Contributor

@magento create issue

@engcom-Hotel engcom-Hotel added the Priority: P2 A defect with this priority could have functionality issues which are not to expectations. label Mar 4, 2025
@engcom-Hotel engcom-Hotel moved this from Ready for Review to Review in Progress in Pull Request Progress Mar 6, 2025
@engcom-Hotel
Copy link
Contributor

@magento run all tests

Copy link
Contributor

@engcom-Hotel engcom-Hotel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @sol-loup,

Thanks for the contribution.

I can see we can handle Simple product scenarios with this, but how can we handle the configurable products.

Also, I guess we can add an automated test for this newly added functionality.

/**
* Controller for Meta Checkout URL implementation
*/
class AddToCartLinkV1 implements HttpGetActionInterface, ActionInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the redundant implementation of ActionInterface. It is already implemented by HttpGetActionInterface.

Comment on lines 83 to 93
public function __construct(
Context $context,
CheckoutSession $checkoutSession,
ProductRepositoryInterface $productRepository,
Cart $cart,
PageFactory $resultPageFactory,
RedirectFactory $resultRedirectFactory,
CouponFactory $couponFactory,
Usage $couponUsage,
ManagerInterface $messageManager
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$qty = $item['qty'];

$product = $this->productRepository->getById($productId);
$this->cart->addProduct($product, ['qty' => $qty]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can pass $product->getId(), instead of passing $product as addProduct method requires either int or \Magento\Catalog\Model\Product parameter

*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a return type declaration.

* @param string $productsParam
* @return array
*/
private function parseProductsParam($productsParam)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a return type declaration and also add a parameter type declaration.

* See COPYING.txt for license details.
*/
-->
<?xml version="1.0"?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this redundant statement

@engcom-Hotel engcom-Hotel moved this from Review in Progress to Changes Requested in Pull Request Progress Mar 6, 2025
@github-project-automation github-project-automation bot moved this to Pending Review in Pull Requests Dashboard Mar 7, 2025
@engcom-Hotel engcom-Hotel moved this from Pending Review to Changes Requested in Pull Requests Dashboard Mar 7, 2025
@engcom-Charlie engcom-Charlie self-assigned this Mar 11, 2025
@engcom-Charlie
Copy link
Contributor

Hi @sol-loup,

Thank you for your contribution!

Can you please look into the review comments and do the needful.

@sol-loup
Copy link
Author

@engcom-Hotel @engcom-Charlie , I've made the requested updates.

I've also added a unit test as requested; test output:

vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist vendor/magento/module-checkout/Test/Unit/Controller/Cart/.
PHPUnit 9.6.20 by Sebastian Bergmann and contributors.

....... 7 / 7 (100%)

Time: 00:00.563, Memory: 10.00 MB

OK (7 tests, 47 assertions)

@sol-loup
Copy link
Author

Hello @sol-loup,

Thanks for the contribution.

I can see we can handle Simple product scenarios with this, but how can we handle the configurable products.

Also, I guess we can add an automated test for this newly added functionality.

This is a good callout; I've adjusted the logic to prioritize sku instead.

This will allow specific configurations to be purchased via SKU -- this will allow most standard configurable variable items to flow through this feature. Customizable products are a special case, and IMO, not well handled even by other e-commerce platforms.

Example here:
https://one.staging.magento-commerce-extension-testing.com/checkout/cart/addtocartlinkv1?products=12345:2

@engcom-Hotel engcom-Hotel moved this from Review in Progress to Changes Requested in Pull Requests Dashboard Apr 7, 2025
@sol-loup
Copy link
Author

sol-loup commented Apr 7, 2025

Hi @sol-loup and @engcom-Bravo,

I have tried to check different scenarios on my local environment on PR branch When I have tried to browse http://39667.local/checkout/cart/addtocartlinkv1?products=abc:5, it gave me correct page.

image But when I have tweaked the URL by adding extra letter before product, like http://39667.local/checkout/cart/addtocartlinkv1?aproducts=abc:5&b=23 then its giving me checkout page but without the item details. I think, we should handle all such scenarios here. image

@sol-loup let me know if I am missing anything. @engcom-Bravo please check it with all the possible scenarios. Till then moving it to Ready for Testing again.

Hey @engcom-Charlie. In this case, you are passing an invalid parameter, and you are just landing on your previous valid cart, whatever that cart may have been. If you feel an explicit error message is more appropriate here, happy to add that.

@engcom-Charlie
Copy link
Contributor

Hi @sol-loup and @engcom-Bravo,
I have tried to check different scenarios on my local environment on PR branch When I have tried to browse http://39667.local/checkout/cart/addtocartlinkv1?products=abc:5, it gave me correct page.
image
But when I have tweaked the URL by adding extra letter before product, like http://39667.local/checkout/cart/addtocartlinkv1?aproducts=abc:5&b=23 then its giving me checkout page but without the item details. I think, we should handle all such scenarios here. image
@sol-loup let me know if I am missing anything. @engcom-Bravo please check it with all the possible scenarios. Till then moving it to Ready for Testing again.

Hey @engcom-Charlie. In this case, you are passing an invalid parameter, and you are just landing on your previous valid cart, whatever that cart may have been. If you feel an explicit error message is more appropriate here, happy to add that.

Hi @sol-loup,

Yes, I have tweaked the URL by passing wrong params. Showing the previous cart on this wrong URL doesn't make any sense. It will be good if we handled it correctly. You can check how such cases are getting handled right now. Eg. either by showing error or most appropriately by showing below page.

image

@sol-loup
Copy link
Author

Hey @engcom-Charlie -- I have applied the change allowing for configuration from the "Checkout" configurations in Magento:
Screenshot 2025-04-16 at 3 18 17 PM

I have also added logic adding a &store param, allowing sellers on multi-site setups to specify which store (if any) they'd like the checkout to occur from.

I have added the noroute behavior in the case where the functionality is disabled. However, I am not comfortable proceeding with the cart deletion logic you have suggested here. This could potentially be a destructive act if the buyer has an active cart session from some other previous shopping session. In the event the Add To Cart Link is invalid, I would prefer not to delete their pre-existing cart.

@engcom-Charlie
Copy link
Contributor

Hi @sol-loup,

Thank you for the updating. @engcom-Hotel Since the review comments have been worked on, moving this PR into review.

@engcom-Hotel
Copy link
Contributor

@magento run all tests

Copy link
Contributor

@engcom-Hotel engcom-Hotel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @sol-loup,

Thanks for making the changes as per the review comment.

Please fix the failed static and unit test failures and some review comments as below.

Functional test failures seems flaky to me and for semantic version checker failure we need to create an internal JIRA for approval.

Thanks

$couponCode = $this->_request->getParam('coupon', '');

// Get quote from checkout session (should now reflect the correct store)
$quote = $this->checkoutSession->getQuote();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling getQuote method may return exception, surround it with try... catch block.

$this->messageManager->addErrorMessage(
__('The coupon code "%1" is not valid or cannot be applied.', $couponCode)
);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This else statement has empty body, I think we can remove it.

$quote = $this->checkoutSession->getQuote();

// Ensure quote is associated with the correct store ID after potential switch
if ($quote->getStoreId() !== $this->storeManager->getStore()->getId()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle NoSuchEntityException for getStore method


// Save quote and collect totals
$quote->collectTotals();
$quote->save();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle Exception for save

if (!empty($couponCode)) {
try {
// Ensure coupon is applied in the context of the potentially switched store
$quote->setCouponCode($couponCode)->collectTotals()->save();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AbstractModel save method has been deprecated, hence please use resource model save method.

// Create Quote mock with all required methods
$this->_quoteMock = $this->getMockBuilder(Quote::class)
->onlyMethods(['removeAllItems', 'addProduct', 'collectTotals', 'save'])
->addMethods(['setCouponCode', 'getCouponCode'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addMethods is deprecated, please see sebastianbergmann/phpunit#5320

use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Message\ManagerInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ObjectManager manager is deprecated, class under test should be instantiated with new keyword with explicit dependencies declaration

@@ -21,6 +21,7 @@
<number_items_to_display_pager>20</number_items_to_display_pager>
<crosssell_enabled>1</crosssell_enabled>
<enable_clear_shopping_cart>0</enable_clear_shopping_cart>
<enable_add_to_cart_link_v1>1</enable_add_to_cart_link_v1>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value for the configuration is set to 1 (enabled). This might introduce unintended behavior for stores unaware of this new feature. So please disable it by default

$parts = explode(':', $pair);
if (count($parts) === 2) {
$identifier = trim($parts[0]);
$qty = filter_var($parts[1], FILTER_VALIDATE_INT); // Validate quantity is an integer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To prevent misuse, please add a maximum limit on the number of products that can be processed in one request.

@engcom-Hotel engcom-Hotel moved this from Review in Progress to Changes Requested in Pull Requests Dashboard Apr 24, 2025
@engcom-Charlie
Copy link
Contributor

Hi @sol-loup,

Did you get a chance to look into above points.

@sol-loup
Copy link
Author

sol-loup commented May 2, 2025

#39667 (comment)

Hey @engcom-Hotel

I'm working through the other suggestions as we speak, but this particular one I'd like to push back on. In order to activate this functionality, the seller / buyer would need to know the exact link path, so there's a low likelihood this would be consumed by accident. Other platforms, such as Shopify, WooCommerce and BigCommerce, already support this behavior fully by default. Is there a problem with turning this on in a similar fashion?

@engcom-Hotel
Copy link
Contributor

Hello @sol-loup,

Regarding this #39667 (comment), this is a completely new feature in Magento. Enabling it silently could lead to store owners being unaware of its presence or impact. That’s why I suggested keeping it disabled by default, allowing store owners to enable it explicitly if they choose to use the feature.

Thanks

@sol-loup
Copy link
Author

sol-loup commented May 6, 2025

Hello @sol-loup,

Regarding this #39667 (comment), this is a completely new feature in Magento. Enabling it silently could lead to store owners being unaware of its presence or impact. That’s why I suggested keeping it disabled by default, allowing store owners to enable it explicitly if they choose to use the feature.

Thanks

@engcom-Hotel

I’d like to keep this flag enabled for a few reasons:

  1. Accidental use is virtually impossible
    a. Path & params: ​/cart/addtocartlinkv1?productId={{id}}:{{qty}}&coupon={{code}} are not guessable. The handler only fires when that exact pattern is present, so routine crawlers, shoppers, or legacy integrations never hit it unintentionally.

  2. Every modern platform already ships it ON
    - Shopify – “Cart permalinks” (public docs).
    - WooCommerce – “Add‑to‑cart URLs” (public docs).
    - Merchants and growth‑teams now expect this behaviour to “just work.” Disabling it by default forces Magento stores to install extensions or patch core, adding friction that our competitors don’t impose.

  3. Business upside > Theoretical downside
    - Campaign lift: agencies report higher CVR when ads land on a pre‑filled cart vs. PDP.
    - Ad‑platform parity: Meta, Google P‑Max, TikTok, and affiliate feeds all generate these links automatically for E-Comm platforms once the endpoint exists. Having it active out‑of‑the‑box means Magento merchants will benefit from the same link behaviors on these platforms without additional configuration.

  4. Opt‑out remains: If a merchant truly doesn’t want the feature, they can disable it in Stores › Configuration › Sales › Checkout › “Enable Cart‑URL links”.

In my opinion, this change aligns Magento with industry checkout norms, removes needless adoption friction, and lets merchants start capturing higher‑intent traffic the moment they upgrade.

Happy to tweak wording or docs, but I believe default‑ON is the lowest‑friction, highest‑ROI path for the ecosystem.

@engcom-Hotel
Copy link
Contributor

Hello @sol-loup,

Thank you for your detailed explanation and for providing industry comparisons and business rationale. Let us discussing this feature internally, meanwhile moving this PR On Hold.

Thanks

@engcom-Hotel
Copy link
Contributor

Hello @sol-loup,

Thank you for your detailed explanation and for providing industry comparisons and business rationale. I appreciate the thoughtfulness behind your reasoning. However, we would like to reiterate some considerations and address your points according to discussion with the internal team:

Accidental Use:

While the URL pattern is specific, we cannot entirely rule out the possibility of unintended activation, especially in edge cases or during system integrations. A disabled-by-default approach ensures that only merchants who fully understand and intend to use the feature will enable it.

Industry Standards:

While Shopify and WooCommerce may have similar features enabled by default, Magento's ecosystem and user base have their own unique expectations and workflows. Silent enablement of new features could lead to confusion, especially for merchants upgrading from an older version who may not be aware of the change.

Business Benefits vs. Merchant Control:

While the business upside is promising, we must prioritize giving merchants control over their stores' functionality. By keeping the feature disabled initially, we empower merchants to make a deliberate decision to enable it, ensuring they fully understand its behavior and implications.

Opt-Out Option:

While it's true that merchants can disable the feature, it's generally better to follow an opt-in model for new functionality, especially when it changes how the platform behaves out of the box. This avoids surprises and ensures a consistent experience for existing users.

Given these considerations, I would still advocate for keeping the feature disabled by default upon release. This approach minimizes potential risks while still allowing merchants to leverage the feature if they see fit. I'd be happy to collaborate further to refine the documentation or messaging around this feature to ensure its value is clearly communicated.

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority: P2 A defect with this priority could have functionality issues which are not to expectations. Progress: on hold Progress: review
Projects
Status: Review in Progress
Development

Successfully merging this pull request may close these issues.

[Issue] Meta Checkout URL Implementation
4 participants