Add the product Subscriptions panel and the PDP plan picker#13
Draft
vbelolapotkov wants to merge 9 commits into
Draft
Add the product Subscriptions panel and the PDP plan picker#13vbelolapotkov wants to merge 9 commits into
vbelolapotkov wants to merge 9 commits into
Conversation
7851657 to
e47095b
Compare
The Subscriptions product-data tab lets a merchant sell a simple or variable product on the storewide selling plans (all of them, or a selected subset) with one-time purchase optionally allowed alongside; all reads and writes go through the engine's SellingPlans facade, and a facade rejection surfaces as a product-screen admin error. The PDP picker renders the one-time vs subscribe choice inside the add-to-cart form from a theme-overridable template, posts the chosen plan as _selling_plan_id, and swaps per-variation option strings from the variation payload over the woocommerce-subscriptions-lite/plan-picker Interactivity API store, built as a script module to build/modules. WOOSUBS-1772
The panel save paths, render markup, picker gating and states, variation payloads, and formatter strings run end to end on the wp-env suite: real nonces and capabilities, real products, plans resolved through the SellingPlans facade from real applicability meta, the packaged template through the real wc_get_template(), and rejections through the real WC_Admin_Meta_Boxes error list. Also migrates the shared plan-seeding helper to the flattened selling-plans catalog (Plan::create with no plan-group argument), which the whole suite now targets. WOOSUBS-1772
…nfig The scripts config previously hand-rolled an ESM build of the customer-portal Interactivity store into build/scripts next to the classic admin bundles, forcing each compilation's output-clean to carve out the other's files with keep regexes. The split is now by runtime system: webpack.scripts.config.js builds only the classic admin bundles (build/scripts), and webpack.modules.config.js builds all Interactivity API view modules - the plan picker and the customer-portal store - via the wp-scripts --experimental-modules variant (build/modules). Each output directory is owned by exactly one compilation, so the standard output-clean applies with no cross-bundle carve-outs. The modules config gains RtlCssPlugin (the upstream module variant emits no RTL stylesheets, and the portal styles are direction sensitive) and the standard output.clean. The portal asset loader now enqueues from build/modules, and the plan picker registers its RTL stylesheet variant. WOOSUBS-1772
Product plan applicability is consumer-owned: the engine keeps the plans catalog and its read facade (list_plans, get_plans), and Lite now owns which products sell on which plans. The new Plans namespace carries the ProductApplicability value object (strict constructor, lenient from_storage), the ApplicabilityStore writing the _wcsl_plans_apply_mode, _wcsl_plans_plan_ids, and _wcsl_plans_allow_one_time product meta with all-or-nothing write validation (simple/variable parents only, plan ids must be active Lite-owned plans), and the ProductPlanResolver mapping a product to its plans through the woocommerce_subscriptions_lite_product_plans filter - Lite's eligibility extension point. The applicability semantics tests move in with the code: the VO contract, the store's meta reconciliation, the write validation, and the resolver modes plus filter behavior all run against the real WordPress test environment. WOOSUBS-1772
…lity layer The product plans panel, the PDP plan picker, and the variation payload filter now call the Lite store and resolver directly instead of the removed engine applicability facade, and the callable constructor seams (reader/writer/lister on the panel, resolver/reader/renderer on the picker, resolver on the variation filter) are gone - the tests exercise the production wiring end to end, shaping resolution through the Lite product-plans filter where they previously injected fakes. The canonical simple/variable type list lives on the store, shared by the picker's render gate and the panel's save gate. WOOSUBS-1772
The engine's catalog read facade dropped its static entry points: consumers now construct an instance scoped to their extension slugs and call list_plans() / get_plans() on it. Each call site constructs the facade in its constructor and holds it on a private property - the read semantics (active plans, slug-scoped, display order) are unchanged. Re-pins the vendored engine to the current flatten-catalog head.
4241847 to
25d2eb9
Compare
- Render the purchase-mode help as a full-width block below the select (WooCommerce floats panel selects; un-float inside the panel) and adopt the clearer copy: the product will use the storewide plans from the subscription settings. - Style the three section headings (Purchase options, Subscription plan selection, One-time purchases) as small uppercase labels and give the blocks a consistent vertical rhythm, tokens from @wordpress/base-styles. - Hide the plans-table checkbox column in all-scope (nothing to pick when every plan applies): the server renders an is-scope-all table class and the toggle script flips it live with the scope radios; the cells stay in the markup, disabled, for the no-reload switch to select-scope. - Nest the all-scope helper inside its radio label so it sits as indented text under the radio instead of a stray full-width block between the two options, and adopt the reference wording. - Give the one-time block a section heading and relabel the checkbox to 'Customers can buy this product without subscribing'. Save contract, field names, nonce, and aria-describedby wiring unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lite half of the buy-path product page slice (WOOSUBS-1772): merchants configure how a product sells (one-time only by default, or on storewide subscription plans - all of them or a selected subset, with one-time purchase optionally allowed alongside), and shoppers see the applicable plans on the product page.
Depends on the engine's flattened selling-plans catalog: woocommerce/woocommerce#66313 (must land and ship in the engine package before this merges; local testing consumes its head via the composer path repository).
Merchant side (product edit screen)
Admin\ProductPlansPanel: a "Subscriptions" product-data tab for simple + variable products - purchase-options mode (sell one-time only / use storewide subscription plans), all-vs-select plan selection (one labeled checkbox row per plan), and an "Allow one-time purchase" toggle (default on). A "Manage plans" link points at the plans manager under WooCommerce > Settings > Subscriptions.Plans\ApplicabilityStore(product meta - mode, attached plan ids, allow-one-time), which validates plan ids against the engine's catalog read facade (Api\SellingPlans, an instance constructed scoped to Lite's slug). The engine stores only the plans catalog; which products a plan applies to is Lite's data. Unsupported product types are skipped; store rejections surface as admin notices viaWC_Admin_Meta_Boxes::add_error()(never thrown - core fireswoocommerce_admin_process_product_objectunwrapped).@wordpress/base-stylestokens and unfloat wp-admin's panel label layout so the fieldset renders in the content pane.Shopper side (product page)
ProductPage\PlanPicker: server-rendered picker onwoocommerce_before_add_to_cart_button(works in classic themes, block themes, and the shortcode), via the overridable templatetemplates/single-product/plan-picker.php. One-time option renders first and preselected when allowed; subscribe-only products get a plain always-enabled select that works without JavaScript. No plans resolved means no output at all.Plans\ProductPlanResolverresolves which plans apply (variation ids resolve to their parent; mode drives the catalog read) and exposes thewoocommerce_subscriptions_lite_product_plansfilter as Lite's eligibility extension point over the resolved set._selling_plan_id- the key the checkout contract-creation handler already reads; actual add-to-cart handling is the next slice.ProductPage\PlanOptionFormatter: engine-computed prices (PricingPolicy::calculate_price()) formatted server-side ($21.60 every 1 month (10% off)); no client-side price math.ProductPage\VariationPlanData: per-variation option HTML injected into thewoocommerce_available_variationpayload (memoized per parent,wp_kses_post-sanitized); the view module swaps option text onfound_variationand restores on reset.client/frontend/plan-picker/view.js); the build is split by runtime system - all Interactivity API view modules (plan picker + customer portal) build through the newwebpack.modules.config.js(build:modules) with@wordpress/interactivityexternalized, classic scripts stay inwebpack.scripts.config.js.Testing
Integration suite (wp-env):
composer test:integration- full suite green (157 tests). Lint:composer lint,npm run lint:js,npm run lint:css. Build:npm run build.Manual (needs the engine flatten branch in the vendored package + 2+ storewide plans seeded under WooCommerce > Settings > Subscriptions):