add website_membership_signup_required: force to create user before add to cart a membership product

This commit is contained in:
Luis 2026-07-07 10:27:13 +02:00
parent 80f24bcc46
commit c4d32c6add
22 changed files with 1202 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,41 @@
/** @odoo-module **/
// Force-redirect handler for the membership signup flow.
//
// The `cart_update_json` controller override returns
// `{ force_redirect: <signup_url> }` when an anonymous visitor tries to add a
// membership product to the cart in "stay on page" mode
// (`website.add_to_cart_action == 'stay'`), which is the JSON path used by
// `_addToCartInPage` in the core `WebsiteSale` widget. The core handler only
// reads `cart_quantity` / `notification_info` from that response and would
// otherwise ignore `force_redirect`, leaving the membership intercept as a
// silent no-op for the JSON path.
//
// The standard `#add_to_cart` button on the product page posts to
// `/shop/cart/update` (HTTP), handled by `cart_update`, which already raises
// an HTTP redirect — that path is unaffected here.
//
// Why a front-end handler and not an HTTP redirect raised from the controller:
// `_addToCartInPage` calls `rpc("/shop/cart/update_json", ...)` and `rpc` does
// `fetch(...).then(r => r.json())` with the default `redirect: "follow"`. A
// `werkzeug.exceptions.Redirect` (3xx) from the JSON route would be silently
// followed by `fetch`, the `/web/signup` HTML would be parsed as JSON, and the
// frontend would break with an uncaught error. The front-end handler is
// therefore the standard, non-intrusive approach (only acts when
// `data.force_redirect` exists).
import "@website_sale/js/website_sale";
import publicWidget from "@web/legacy/js/public/public_widget";
publicWidget.registry.WebsiteSale.include({
/**
* @override
*/
async _addToCartInPage(params) {
const data = await this._super.apply(this, arguments);
if (data && data.force_redirect) {
window.location.href = data.force_redirect;
}
return data;
},
});

View file

@ -0,0 +1,112 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import * as tourUtils from "@website_sale/js/tours/tour_utils";
const PRODUCT_NAME = "Membership Test Product";
registry.category("web_tour.tours").add("website_membership_signup_required_tour", {
url: "/shop",
steps: () => [
// -- Anonymous visitor, add a membership product to cart -----------
{
content: "Search the membership product",
trigger: 'form input[name="search"]',
run: `edit ${PRODUCT_NAME}`,
},
{
content: "Run the search",
trigger: 'form:has(input[name="search"]) .oe_search_button',
run: "click",
expectUnloadPage: true,
},
{
content: "Open the membership product page",
trigger: `.oe_product_cart:first a:text(${PRODUCT_NAME})`,
run: "click",
expectUnloadPage: true,
},
{
content: "Click Add to Cart as anonymous user",
trigger: "#add_to_cart",
run: "click",
expectUnloadPage: true,
},
// -- On /web/signup with firstname / lastname ----------------------
{
content: "Signup page shows firstname field (b2c re-enabled by marker)",
trigger: ".oe_signup_form input[name='firstname']",
run: "edit Tour",
},
{
trigger: ".oe_signup_form input[name='lastname']",
run: "edit Member",
},
{
content: "Type a unique email login",
trigger: ".oe_signup_form input[name='login']",
run: () => {
const email = `membership.tour.${Date.now()}@test.example.com`;
document.querySelector(".oe_signup_form input[name='login']").value = email;
},
},
{
trigger: ".oe_signup_form input[name='password']",
run: "edit TourMember1!",
},
{
trigger: ".oe_signup_form input[name='confirm_password']",
run: "edit TourMember1!",
},
{
content: "Submit the signup form -> redirect back to product page",
trigger: ".oe_signup_form button[type='submit']",
run: "click",
expectUnloadPage: true,
},
// -- Back on the product page, now authenticated --------------------
{
content: "We're back on the membership product page (authenticated)",
trigger: `#product_details h1:contains("${PRODUCT_NAME}")`,
},
{
content: "Add to cart as authenticated user",
trigger: "#add_to_cart",
run: "click",
expectUnloadPage: true,
},
tourUtils.goToCart(),
tourUtils.assertCartContains({ productName: PRODUCT_NAME }),
],
});
registry.category("web_tour.tours").add("website_membership_signup_required_non_intercept_tour", {
url: "/shop",
steps: () => [
{
content: "Search the regular product",
trigger: 'form input[name="search"]',
run: "edit Regular Test Product",
},
{
content: "Run the search",
trigger: 'form:has(input[name="search"]) .oe_search_button',
run: "click",
expectUnloadPage: true,
},
{
content: "Open the regular product page",
trigger: `.oe_product_cart:first a:text("Regular Test Product")`,
run: "click",
expectUnloadPage: true,
},
{
content: "Add to cart as anonymous (no signup interception)",
trigger: "#add_to_cart",
run: "click",
expectUnloadPage: true,
},
tourUtils.goToCart(),
tourUtils.assertCartContains({ productName: "Regular Test Product" }),
],
});