42 "summary": "plan.status is REQUIRED on create and is NOT listed among the docs' required parameters. VERIFIED LIVE 2026-08-16: omitting it 400s with REQUIRED_FIELD on plan.status; 'ACTIVE' is accepted. Any generated create payload built strictly from the documented required-field list will fail on the first call."
43 },
44 {
45 "code": "no-archive-on-v3",
46 "severity": "warning",
47 "summary": "Plans V3 has no archive endpoint — POST /pricing-plans/v3/plans/{id}/archive 404s. VERIFIED LIVE 2026-08-16: the working call is the V2 one, POST /pricing-plans/v2/plans/{id}/archive → 200, after which the V3 GET shows archived: true while status stays ACTIVE. Rollback/cleanup for a plan import therefore crosses API versions, and 'archived' and 'status' are independent flags — never infer one from the other."
48 },
49 {
50 "code": "private-forces-buyable-false",
51 "severity": "warning",
52 "summary": "VERIFIED LIVE 2026-08-16: creating a plan with visibility PRIVATE returns buyable: false even when buyable was not sent. Recreated contracts still work (offline orders ignore buyability), but a plan imported PRIVATE for a staged rollout will not become self-purchasable just by flipping visibility later — check buyable explicitly after any visibility change."
53 },
54 {
55 "code": "billing-cycle-minimum-7-days",
56 "severity": "blocker",
57 "summary": "billingCycle must be at least 7 days and can't exceed 10 years (error VALID_BILLING_CYCLE). The period enum includes DAY, but a source cadence of every 1-6 days cannot be recreated; surface affected offers before import."
58 },
59 {
60 "code": "plan-duration-max-10-years",
61 "severity": "warning",
62 "summary": "Total plan duration (billingCycle x billingCycleCount) can't exceed 10 years (error VALID_PLAN_DURATION); billingCycleCount is capped at 2000. Very long fixed-length source subscriptions must fall back to UNTIL_CANCELLED with a ledger entry."
63 },
64 {
65 "code": "single-variant-single-strategy",
66 "severity": "warning",
67 "summary": "Docs state pricing variants are 'Currently limited to 1 variant per plan', and pricingStrategies is validated minItems 1 / maxItems 1 with flatRate as the only strategy. Tiered, usage-based, or multi-cadence pricing on one source offer must be split into separate plans or dropped with a ledger entry."
68 },
69 {
70 "code": "plan-name-max-50-chars",
71 "severity": "warning",
72 "summary": "plan.name is limited to 50 characters (description to 450). Long source product names truncate; keep the full source name and product reference in the description."
73 },
74 {
75 "code": "currency-is-site-level",
76 "severity": "warning",
77 "summary": "plan.currency is read-only (ISO-4217, driven by site settings). A source subscription priced in a different currency imports its numeric amount under the site currency; verify the site currency matches the source before import (error INVALID_AMOUNT rejects more decimal places than the plan currency supports)."
78 },
79 {
80 "code": "public-plans-render-immediately",
81 "severity": "warning",
82 "summary": "visibility is a required create field; PUBLIC plans appear on the live site's Plans & Pricing page as soon as they are created. Import with the intended final visibility deliberately - PRIVATE keeps a plan off the page while existing buyers can still use it, and buyable:false blocks self-purchase."
83 }
84 ],
85 "mappingGuidance": [
86 "Cadence maps 1:1: billingTerms.billingCycle.period enum is DAY | WEEK | MONTH | YEAR, matching WooCommerce day|week|month|year (e.g. wps_sfw_subscription_interval); billingCycle.count (string, min 1) carries the interval number.",
87 "Open-ended source subscriptions -> endType UNTIL_CANCELLED. A source expiry/length -> endType CYCLES_COMPLETED with cyclesCompletedDetails.billingCycleCount = expiry divided by the cadence (must divide evenly; ledger the remainder otherwise). startType ON_PURCHASE.",
88 "Required create fields: plan, plan.status (undocumented but enforced — see status-required-on-create), pricingVariants[].id (GUID), .name, .billingTerms (billingCycle.period, startType, endType), plan.visibility; pricingStrategies[0].flatRate.amount is the recurring price as a decimal string (>= 0, max 4 decimal places; 0 makes a free plan). A client-minted pricingVariants[].id GUID is preserved verbatim (verified live 2026-08-16), so the variant id can be derived deterministically from the source offer id and used as a crosswalk key.",
89 "A source initial signup fee maps to pricingVariants[].fees[]: priceType FIXED_AMOUNT (fixedAmountOptions.amount > 0), appliedAt FIRST_PAYMENT - the only documented fee type and application point.",
90 "freeTrialDays (integer 0-999) carries the free trial; it is days only, so convert week/month source trials to days.",
91 "Deduplicate to one plan per distinct source offer (product x cadence x price) shared by all its subscribers - never one plan per subscriber.",
92 "Pass idempotencyKey (must be GUID format) derived deterministically from the source offer id (e.g. UUIDv5) so a retried import returns the original plan instead of duplicating it.",
93 "The Stores-product linkage is severed by design: record the source product name/id in the plan title/description (required migration trade-off). extendedFields exists on the plan but requires a schema-plugin app namespace, so it is not a free-form carry.",
94 "Definitions are built by lib/pricing-plan-definition.js (buildPlanDefinition: PRIVATE, buyable false, status ACTIVE, name<=50/description<=450 truncated WITH a finding, 7-day minimum cycle and 10-year maximum enforced as unmappable/finding) and written by wix-writers.js ensurePlanDefinition: the reconciliation identity is the client-minted pricingVariants[].id -- a crosswalk row is read back and adopted only if the plan exists, is not archived and carries it; then Query Plans by name filtered to plans carrying it (one reconciles, more is ambiguous and creates nothing); then Create Plan >=1s apart. The row-count gate (evaluatePlanSources over B1/B2/B3) runs first: all zero means 'no pricing plans found', never 'not supported'."
95 ],
96 "setupRequirements": [
97 "Wix Pricing Plans app installed — appDefId 1522827f-c56c-a5c9-2ac9-00f9e6ae12d3 (ground truth: the appId echoed by the 428 APP_NOT_INSTALLED error). Verified live 2026-08-16: it is NOT present on a commerce-template site by default, and it installs cleanly and dependency-free through the standard App Installation API (POST /apps-installer-service/v1/app-instance/install), so this is an automatable setup step, not a manual one. Until it is installed, every Plans V3 read/write returns 428 APP_NOT_INSTALLED.",
119 "note": "Verified live: the Pricing Plans app must be installed before Plans V3 writes. Create and GET readback preserved the client-supplied variant ID, billing terms, and flat-rate strategy; Wix derived the slug and site currency and supplied the documented defaults. plan.status is required on create, and archival uses V2 because V3 has no archive endpoint."
124 "note": "2026-09-05, fresh disposable site with the Pricing Plans app installed at creation (Create Project `apps`): ensurePlanDefinition -> Create Plan V3 with visibility PRIVATE, buyable false, status ACTIVE, one MONTH variant with freeTrialDays 7 and a client-minted variant id -> 200; GET reads back PRIVATE / buyable:false / ACTIVE; Query Plans with `filter: { name: { $eq } }` finds it so a re-run reconciles by name and creates nothing; POST /pricing-plans/v2/plans/{id}/archive -> 200."
129 "note": "2026-09-05 (fresh site): reconciliation is by the client-minted `pricingVariants[].id`, not the name — the Query Plans read-back carries the variant id verbatim and the re-run reported `matchedBy: variant-id`, creating nothing. GET /pricing-plans/v3/plans/{id} is the read used to verify a crosswalk row before it is adopted (404 -> stale row). A name is only a candidate filter: names are cut to 50 chars and collide across offers."
130 }
131 ],
132 "notes": "WooCommerce active subscriptions migrate by recreation as Pricing Plans memberships: this entity is the offer half (one plan per distinct source offer), and pricing-plans/plan-order is the per-subscriber contract half. Live verification covered install, create, readback, and archive for a WEEK/UNTIL_CANCELLED/flatRate plan. CYCLES_COMPLETED, fees[], freeTrialDays, and the documented VALID_BILLING_CYCLE 7-day floor remain unverified, and plans can be archived but not deleted; reliability therefore remains partially-reliable. The recurring offer also has a live-verified field home at stores/product.subscriptionDetails, which keeps the product purchasable while this plan anchors recreated contracts."