-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
base: 2.4-develop
Are you sure you want to change the base?
Conversation
…ta Checkout URL implementation
Hi @sol-loup. Thank you for your contribution!
Allowed build names are:
You can find more information about the builds here For more details, review the Code Contributions documentation. |
@magento run all tests |
@magento create issue |
@magento run all tests |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
public function __construct( | ||
Context $context, | ||
CheckoutSession $checkoutSession, | ||
ProductRepositoryInterface $productRepository, | ||
Cart $cart, | ||
PageFactory $resultPageFactory, | ||
RedirectFactory $resultRedirectFactory, | ||
CouponFactory $couponFactory, | ||
Usage $couponUsage, | ||
ManagerInterface $messageManager | ||
) { |
There was a problem hiding this comment.
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]); |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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"?> |
There was a problem hiding this comment.
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
Hi @sol-loup, Thank you for your contribution! Can you please look into the review comments and do the needful. |
@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/. ....... 7 / 7 (100%) Time: 00:00.563, Memory: 10.00 MB OK (7 tests, 47 assertions) |
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: |
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. ![]() |
Hey @engcom-Charlie -- I have applied the change allowing for configuration from the "Checkout" configurations in Magento: 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. |
Hi @sol-loup, Thank you for the updating. @engcom-Hotel Since the review comments have been worked on, moving this PR into review. |
@magento run all tests |
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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']) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
Hi @sol-loup, Can you please have a look into the review comments as well as fix the build failures. Static test failures: https://public-results-storage-prod.magento-testing-service.engineering/reports/magento/magento2/pull/39667/1fdd1741792c608604490da5fac3f74b/Statics/console-error-logs.html Unit test failures: https://public-results-storage-prod.magento-testing-service.engineering/reports/magento/magento2/pull/39667/a3e18407c90d68f9ce142440a79d4183/Unit/console-error-logs.html |
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? |
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 |
I’d like to keep this flag enabled for a few reasons:
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. |
…to 2.4-develop
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 Thanks |
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 |
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
AddToCartLinkV1
controller that processes URL parameters to populate the cartHow It Works
The controller accepts URL parameters in this format:
/checkout/cart/addtocartlinkv1?products=123:2,456:1&coupon=SAVE10
This will:
The implementation handles error cases gracefully, showing appropriate messages for invalid products or coupons.
Resolved issues: