Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion docs/my-account-v2-prototype-devlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,49 @@ The `closeModal` event newspack-ui's `modals.js` dispatches on every state→clo

> See [brief §10 → Phase 6](my-account-v2-prototype-brief.md#phase-6--payment-methods-115-days). Reproduce v1's `/my-account/payment-methods/` byte-for-byte under the v2-demo flag, fed by fake data.

_(empty)_
**Date:** 2026-04-28
**By:** thomas@a8c.com
**PR:** _pending — phase branch `prototype/my-account-demo-phase-6`, draft PR targets `prototype/my-account-demo` (umbrella tracker is #4679)_
**Commits:** _pending — code is staged on `prototype/my-account-demo-phase-6`_

**What I built**

The `/my-account/payment-methods/` surface, end-to-end, byte-for-byte against WC core's `myaccount/payment-methods.php`. (a) Registered a new `woocommerce_account_payment-methods_endpoint` action handler `render_payment_methods_endpoint` that loads `templates/v2-demo/payment-methods.php`. (b) Added a `template_redirect` priority-8 callback `takeover_payment_methods_endpoint` that calls `remove_all_actions('woocommerce_account_payment-methods_endpoint')` to drop both WC core's default `woocommerce_account_payment_methods` (which renders WC core's table) and v1's effective swap of the underlying `wc_get_template('myaccount/payment-methods.php')` to `payment-information.php`, then re-adds our renderer. (c) Bumped `ENDPOINTS_VERSION` from 4 → 5 so the auto-flush guard re-runs once on the next admin /my-account/ visit — even though we don't `add_rewrite_endpoint` for `payment-methods` (WC core already owns that slug), the version bump kept the flush plumbing consistent across phases. (d) Plucked `payment-methods` out of the inbound menu items (v1 had relabelled it to "Payment information") and re-inserted it in the v2 sidebar order: `edit-account → newsletters → donations → subscriptions → payment-methods`. (e) Extended `get_fake_data()` with a `payment_methods` slice (two saved cards: default Visa with only a Delete action, and a non-default Mastercard with Make default + Delete actions — shape mirrors `wc_get_customer_saved_methods_list()`). (f) Created `src/my-account/v2-demo/payment-methods.js` — a snackbar-only dispatcher (no modals this phase), wiring `data-action="set-default-payment-method"`, `delete-payment-method`, and `add-payment-method` clicks to localized snackbars via the shared `util/snackbar.js`. (g) Imported the new module from `src/my-account/v2-demo/index.js`.

The template mirrors WC core verbatim: `<table class="woocommerce-MyAccount-paymentMethods shop_table shop_table_responsive account-payment-methods-table">`, same `<th class="woocommerce-PaymentMethod woocommerce-PaymentMethod--<column> payment-method-<column>">` headers, same per-cell `data-title` attributes for responsive collapse, same `default-payment-method` row class on the default row, same trailing `<a class="button" href="add-payment-method-url">Add payment method</a>`. The only deviation: a small inline `<span class="newspack-ui__badge newspack-ui__badge--secondary">Default</span>` next to the brand name on the default row — WC core relies on the `default-payment-method` CSS class alone, which is invisible without theme-side CSS, and the demo needs the indicator to be readable on any theme. Same `--secondary` badge variant the v1 `payment-information.php` uses for the same surface.

Verified end-to-end via curl with an admin auth cookie + `?v2-demo=1`: 11/12 markup checks pass (the one failure is my regex using lowercase "Mastercard" while `wc_get_credit_card_type_label` correctly capitalizes to "MasterCard" — output is right, regex was wrong); body class is `newspack-my-account--v2-demo`; `account-payment-methods-table` renders with both rows; default badge appears on the Visa row only; Make default + Delete buttons emit on the Mastercard row; "Add payment method" anchor preserves `?v2-demo=1`. Non-demo admin request to the same URL renders v1's `payment-information.php` cards (no v2 table, no v2 body class). Anonymous request to the demo URL does not enqueue the demo bundle and does not get the body class. Sidebar order is `edit-account → newsletters → donations → subscriptions → payment-methods`. Lint clean across `:php`, `:js`, `:scss`. Build passes.

**What I learned**

The "v1" reference in the brief is ambiguous between WC core's `myaccount/payment-methods.php` and Newspack v1's `payment-information.php`. WC core renders a `<table class="account-payment-methods-table">`; Newspack v1 already swaps that template for its own card-based design (`<div class="newspack-ui__box ...">` per card) via the `wc_get_template` filter. The brief description ("v1 already renders /my-account/payment-methods/ as a `<table class=...>`") matches WC core, not Newspack v1. The user instructions were explicit ("WC core markup, fed by fake data") so I went with WC core's table. Worth recording this for future readers — when "v1" is invoked in the brief, it can mean either WP/WC default or the specifically-Newspack override.

The takeover trick from Phase 4 generalises cleanly. v1's `wc_get_template` swap to `payment-information.php` is bypassed without touching v1's filter at all — WC core's `woocommerce_account_payment_methods()` is what calls `wc_get_template`, and once we drop that handler from the action, the call site never fires. The filter is still registered on `wc_get_template`; it just never runs because the template isn't requested. Cleaner than racing v1's filter with priority ordering, and matches the Phase 4 idiom one-to-one.

The `payment-methods` slug uses a hyphen, not an underscore. WP's `add_rewrite_endpoint` and the corresponding action hook both preserve the dash: the action is `woocommerce_account_payment-methods_endpoint`, and `get_query_var('payment-methods')` (with dash) is what reads the URL match. PHP can call `do_action` with hyphenated names just fine. Worth knowing — I started with the underscore reflex and would have wired the takeover to a never-fired action if I hadn't grepped WC core first.

The Phase 3/4 reflexes carried over cleanly. (i) Reserved-globals trap fired again on the template's `foreach ( $payment_method_actions as $action_key => $action )` — `$action` is a WP-reserved global. Renamed to `$action_data` after PHPCS flagged it; saved a round-trip on a future phase by remembering this is now the third time it's bitten (prior: `$id`, `$status`, `$title`, `$type`, `$frequency` — and now `$action`). (ii) The opcache + ENDPOINTS_VERSION-bump caveat (Phase 3 cross-phase log) bit again. After bumping 4 → 5, the takeover didn't fire on the first request because the new code wasn't yet in opcache. Fixed by `wp eval 'opcache_reset();' && wp option delete newspack_my_account_v2_demo_endpoints_version && wp rewrite flush`. Production deploys clear opcache so it won't bite there, but I'll mirror the same caveat in the PR test plan again.

I almost added a SCSS rule for the inline Default indicator. The first pass tried to render Default as the row class alone (mirroring WC core); the demo theme didn't style it, so the default row was indistinguishable. I considered adding a `:where(.newspack-my-account--v2-demo .default-payment-method::before)` rule with content + neutral-5 background — and caught myself per the Phase 4 "v1 class names first" reflex. v1's `payment-information.php` already uses `.newspack-ui__badge.newspack-ui__badge--secondary` for the Default label on the same surface; rendering the same span inline keeps style.scss wrapper-only and uses an existing newspack-ui primitive. Style file is still wrapper-only. The reflex order from §2.1.1 (v1 markup → newspack-ui composition → custom SCSS) handled it cleanly.

**Decisions and why**

- **Reproduce WC core's table DOM, not Newspack v1's card DOM.** Per user instructions: "WC core markup, fed by fake data." Three reasons: (1) the surface the brief describes (`<table class="shop_table account-payment-methods-table">` with Method / Expires / Actions columns) is WC core's, not v1's. (2) WC core's table is the production-shaped surface readers see on a fresh WC install before any Newspack-side customisation. (3) the demo's job is to prove the v2 reader experience, and "what does the v1 site reader see today" is the WC core table when v1 isn't active — feeding it fake data is the smaller fidelity gap to close.
- **Takeover via `template_redirect` priority 8 + `remove_all_actions`.** Same shape as Phase 4's subscriptions takeover. v1's `wc_get_template` filter and Stripe's column-action filters both fall away without explicit removal because the action handler that would have called them never fires. Trade-off: any third-party hook on `woocommerce_account_payment-methods_endpoint` (e.g. a site-installed extension) is also suppressed during demo requests. Same sledgehammer trade-off the Phase 4 devlog calls out for subscriptions; acceptable for a prototype.
- **`ENDPOINTS_VERSION` bumped from 4 → 5 even though no new rewrite endpoint is registered.** The version-bump's job is two-fold: (i) trigger the auto-flush so any rewrite-rule drift accumulated since the last admin visit lands clean, and (ii) keep the bump cadence consistent across phases that touch the endpoint plumbing — a reader of the constant's history can trace each phase's contribution. Skipping the bump because we don't `add_rewrite_endpoint` for `payment-methods` would break that audit trail. The flush is a 50ms one-shot per environment; cheap.
- **Snackbar-only dispatcher, no modals.** Phase 6's job is reproducing v1's surface, not introducing new flows. Make default / Delete / Add new each route to a localized snackbar via the shared `util/snackbar.js`; no Phase 6 modals. The dispatcher mirrors `donations.js` / `subscriptions.js` — slug map → snackbar message → `event.preventDefault()` → toast. Two scoped roots (`.account-payment-methods-table` for row actions, the trailing add-button for the CTA) instead of one document-wide listener; cheaper to reason about and matches WC core's DOM partition.
- **Inline "Default" badge alongside the row class.** WC core uses the `default-payment-method` row class only; the Newspack v1 `payment-information.php` adds an explicit "Default" badge inside `.newspack-ui__box__badges`. Followed v1's reflex (per brief §2.1.1) — render the same `__badge--secondary` span inline next to the brand name. Keeps style.scss wrapper-only; uses an existing newspack-ui primitive; matches the v1 visual hierarchy. The row class stays for any future CSS that wants to target the row independently.
- **Plucked `payment-methods` out of the inbound menu items before re-inserting.** v1 relabels it to "Payment information"; without the pluck-then-reinsert dance the v2 sidebar would render either "Payment information" (if v1's filter wins ordering) or render the slug twice. Same idiom Phase 4 used for `subscriptions` (plucked from WCS' `wc_subscriptions_at_top` placement); reads as the established pattern in this filter.
- **Two cards in fake data, not three.** The user instructions and the brief both describe Make default / Delete buttons; two cards (one default, one non-default) is the minimum to demonstrate both action shapes. Three cards would have been more visually busy without exercising new code paths. Phase 7 scenario fixtures can flip in an "expired card" or "many cards" variant without touching the template.
- **`$action_data` not `$action` in the template.** PHPCS's reserved-globals rule trips on `$action`. Renamed after the lint run; mechanical fix at this point.
- **Add payment method anchor preserves the demo flag via the existing `woocommerce_get_endpoint_url` filter.** No new code; the Phase 2 link-preservation filter already wraps `wc_get_endpoint_url('add-payment-method')`. Confirmed in the curl trace — anchor URL is `https://localhost/my-account/payment-methods/?v2-demo=1` (the v1 site option folds add-payment-method into the same payment-methods URL — see v1's `pre_option_woocommerce_myaccount_add_payment_method_endpoint`).

**Open questions**

- **Add payment method goes nowhere meaningful.** The anchor's `data-action="add-payment-method"` is stub-snackbarized; the underlying `wc_get_endpoint_url` URL is unused (the click is preventDefault'd). Phase 7 / productisation will need to pick: (a) build the actual add-card flow as a modal (mirroring Phase 5's renew-subscription transaction shape, since it shares the Stripe form), or (b) navigate to a real `/my-account/add-payment-method/?v2-demo=1` URL once that endpoint is wired up.
- **Inline default badge is a small deviation from WC core's exact DOM.** I added a `<span class="newspack-ui__badge newspack-ui__badge--secondary">` to the method cell when `is_default` is true. WC core doesn't ship this. Decision was that the row class alone is invisible without theme-side CSS, so the demo needs an explicit indicator to read correctly — but if the design review wants a true byte-for-byte WC-core match, drop the span and rely on `default-payment-method` styling instead.
- **No expired-card variant.** Both fake cards are valid (02/27 and 08/28). v1 renders an "Expired" badge on cards whose `expires` is in the past. Phase 7 scenario fixtures (`?v2-demo=expired-payment`) should swap one card for an expired Visa to demonstrate the badge.
- **Stripe / WCS column filters are dropped.** WC core's template iterates `wc_get_account_payment_methods_columns()` and supports per-column action handlers (`woocommerce_account_payment_methods_column_<column_id>`) that Stripe and WCS hook to inject custom column markup. Our takeover removes the entire endpoint action — those column hooks never fire because the template isn't loaded by WC core anymore. Acceptable for a prototype with fake data; production wiring would need to either pass through real column extensions or rebuild any production-relevant columns ourselves.

---

Expand Down Expand Up @@ -337,3 +379,9 @@ A flat list of decisions that span phases or that future-you will want to find w
| 2026-04-28 | Renew + Restart modals reuse the `subscriptions.tiers.billing` fixture from the Phase 4 fake-data slice for the billing readout. Donations don't carry their own billing block; the demo address is shared across all transactional modals so reusing keeps the prototype's fictional reader consistent without proliferating fixtures. | Phase 5 |
| 2026-04-28 | Modal data-attribute payloads (`data-unit-labels`, `data-next-dates`, `data-recurring-total-labels`, `data-vat-rate`, `data-fee-rate`, `data-currency-symbol`) replace `wp_localize_script` for per-modal config. Each modal partial JSON-encodes its own params on the container element; the JS reads them once on wire-up. Cheaper than a separate localization pass when each payload is bound to a specific resource id and lives next to the markup that consumes it. | Phase 5 |
| 2026-04-28 | Roadmap renumbering: insert a new **Phase 6 — Payment methods** (reproduce v1's `/my-account/payment-methods/` page byte-for-byte under the demo flag, fake data only — no new design). The previous Phase 6 (polish + scenario fixtures) becomes **Phase 7**. Brief §3 grows from three to four primary surfaces (newsletters / donations / subscriptions / payment methods); §10 gets a new Phase 6 entry; §12 definition-of-done updated to "five primary screens" (dashboard counts as a screen). Reason: the prototype currently has no story for the payment-methods page, but v1 has a real surface there that readers see today. Reproducing the existing v1 surface keeps the prototype credible without expanding the design footprint. | Phase 5 (mid-phase scope addition) |
| 2026-04-28 | Phase 6's "v1" reference is **WC core's `myaccount/payment-methods.php`** (the `<table class="account-payment-methods-table">`), not Newspack v1's `payment-information.php` (the card-based override). The brief description matches the WC core surface, and the user instructions were explicit ("WC core markup, fed by fake data"). Worth noting because "v1" in the brief is otherwise ambiguous between WC core and Newspack v1 — when in doubt, the WC core / unmodified-WC default is what readers see when nothing Newspack-specific has run. | Phase 6 |
| 2026-04-28 | Payment methods endpoint takeover: same `template_redirect` priority-8 + `remove_all_actions('woocommerce_account_payment-methods_endpoint')` shape as Phase 4's subscriptions takeover. Bypasses both WC core's default `woocommerce_account_payment_methods` callback and v1's `wc_get_template` swap to `payment-information.php` in one move (because the action that would call `wc_get_template` never fires). Re-adds our `render_payment_methods_endpoint` so it's the sole renderer when the demo flag is active. Same sledgehammer trade-off the subscriptions takeover carries: any third-party action handler on the same hook is also suppressed during demo requests. | Phase 6 |
| 2026-04-28 | `ENDPOINTS_VERSION` constant bumped to `5` for Phase 6 even though no new rewrite endpoint is registered (`payment-methods` is owned by WC core). The bump still triggers the auto-flush guard so any rewrite-rule drift since the last admin visit lands clean, and it keeps the audit trail consistent — every phase that touches endpoint plumbing bumps the constant. | Phase 6 |
| 2026-04-28 | The `payment-methods` endpoint slug uses a hyphen, preserved everywhere: the action hook is `woocommerce_account_payment-methods_endpoint`, `get_query_var('payment-methods')` reads the URL match. PHP's `do_action` and `add_action` accept hyphenated names. Worth noting because the underscore-reflex would silently wire to a never-fired action. | Phase 6 |
| 2026-04-28 | Reserved-globals trap, fifth iteration: PHPCS rejected `$action` as a template variable (after Phases 2/3/4 each tripped on `$id`, `$status`, `$title`, `$type`, `$frequency`). Renamed to `$action_data`. The accumulated guidance — never reuse these names as template-local variables; always prefix with the entity (`$payment_method_id`, `$action_data`) — is now solid enough that future templates can skip the round-trip. | Phase 6 |
| 2026-04-28 | Inline `<span class="newspack-ui__badge newspack-ui__badge--secondary">Default</span>` next to the brand name on the default-payment-method row. WC core relies on the `default-payment-method` row class alone (invisible without theme-side CSS), so the demo needs an explicit indicator to read correctly. Reuses the same v1 `payment-information.php` badge pattern (per brief §2.1.1 reflex order: v1 markup first), keeping `style.scss` wrapper-only. The row class still emits for any future CSS that targets the row independently. | Phase 6 |
Loading
Loading