Skip to content

PayPal Payment Buttons: move the product form into the block inspector - #52022

Draft
tx2pnw wants to merge 23 commits into
add/paypal-payment-buttons-v2from
update/paypal-buttons-form-to-inspector
Draft

PayPal Payment Buttons: move the product form into the block inspector#52022
tx2pnw wants to merge 23 commits into
add/paypal-payment-buttons-v2from
update/paypal-buttons-form-to-inspector

Conversation

@tx2pnw

@tx2pnw tx2pnw commented Sep 4, 2026

Copy link
Copy Markdown

Fixes WOOPTP-488

Proposed changes

Moves the product form fields off the editor canvas into the block inspector, fixes how the same controls look at the sidebar's 280px width, and fixes bugs in the form itself — including one that silently destroyed merchant configuration.

The move

One field or group per commit; the canvas form keeps working and shrinks as fields move. The last field out is what forces a preview onto the canvas, so until then the block keeps a shrinking form there — that's expected, not half-done work. Location only.

  • Product name — new Details panel in the block inspector
  • Price and currency — same panel
  • Description
  • Product image
  • Return URL — its own URL Redirect panel, collapsed by default
  • Checkout options (quantity, tax, custom fields)
  • Variants — its own Product Options panel. Option rows always stack now. Three across never fit a 200px column
  • Display format — resolves a duplicate switcher; one control across create, edit and preview
  • Button appearance — its own panel, collapsed by default
  • Form actions — the primary action and Cancel. The only control in the sidebar that commits, so it renders below every panel, outside any PanelBody. The canvas takes the button preview, the status row and the notices
  • Dead CSS sweep — remove rules orphaned by the moves

Appearance

Same controls, same attributes, same PayPal payload — only how they look in the column changes.

  • Display Format — drops the duplicate label, and the button group fills the column, lined up with Style's
  • Details — Product Image reads uppercase like the labels above it. The description counter moves onto its own line, and DESCRIPTION gets the 16px gap the others have
  • Product Options — option rows sat behind three left edges, now one border. The fields gain 16px
  • Currency — every label included the currency's full name, so the select read USD — US.... Now the code and its symbol, USD $, like Simple Payments and Donations. Widest is 61px in 66px of room, so all 24 fit
  • Form actions — the primary button crossed Create/Update with the display format, for six labels. It now reads Create New, or Save once a button exists
  • Panel order — URL Redirect, one optional field, sat above Checkout Options and Product Options. Now Details / Product Options / Checkout Options / URL Redirect
  • Tax fields — the tax controls sat in a bordered box 26px narrower than the toggles above them, and Use PayPal profile settings truncated in the narrow sidebar. Now full width and flush with the toggles
  • Panels on load — three opened at once, two of them styling panels near the bottom, so the sidebar opened over 1400px tall. Now Details alone when editing, Details and Product Options when creating, Style when previewing

Behavior

Everything above is a move or a pixel. These change what the merchant gets.

  • Editing a button showed an error after the edit saved

PayPal_API_Client::update_resource() expected HTTP 200. PayPal answers 204 No Content on a successful PUT, and make_request() takes only the matching status as success — everything else goes down the error path. So PayPal saved the edit and the merchant got paypal_api_unknown_error and "please try again".

The client now takes 204 and returns the request data plus the resource id, since a 204 has no body to parse. A re-GET would also work, but it adds a call that can fail on its own, and a GET that fails after a saved PUT recreates this same bug. extract_payment_link() and validate_paypal_url() come out of that method with it: no body means no links to read.

That return value is what was sent, not what PayPal stored — a PUT is a full replacement and PayPal resets whatever the request left out. Use get_resource() for real state.

200 is accepted alongside 204 as insurance. Every measured PUT is 204 with an empty body, including under Prefer: return=representation, a replayed PayPal-Request-Id, and every Accept we tried. The response is discarded either way, so accepting 200 costs nothing, and guessing this too narrowly is what caused the bug in the first place. The 200 came from PayPal's own guide, which said "Returns 200 OK with the updated payment link details" until PayPal corrected it to 204 No Content.

Fixing the form now that it lives in the inspector

Ten fixes to the controls themselves, one per commit. Unlike everything above, each changes what a control does, and two remove one.

  • Update destroyed configuration the form has no controls for
  • Tax name field out, along with the check that dropped tax whenever it was blank
  • Price field hidden under per-variant pricing
  • Custom checkout fields capped at 2, PayPal's own limit
  • Shared-link warning always on
  • Option errors get somewhere to show
  • Tax rate required when tax is on
  • Description limit 256 → 2048, PayPal's real limit
  • Return URL page picker
  • Per-variant pricing fixed to the first group

Update destroyed configuration the form has no controls for

A PUT replaces the whole resource, and buildRequestData only sends the fields the form has controls for. PayPal deleted the rest. Same link, one edit:

Field Before After one Update
product_id SKU-1 gone
shipping FLAT 5.00, +2.00 per extra unit gone
handling FLAT 4.00 gone
discounts FLAT 2.00 gone
collect_shipping_address false true

It happened silently, the block said it worked, and there was nothing to undo. This hit any link the block did not create — one made in PayPal's dashboard, or by the admin page.

handleUpdateButton now reads the payment first, merges those five fields into the request, then PUTs. Costs one GET per save, and it picks up an edit made in PayPal's dashboard since the block loaded. PayPal's value wins over the form's for all five: the form has no control for any of them, so what it would send is a default, not a decision.

Two supporting changes, both needed or the JS achieves nothing:

  • sanitize_line_items() rebuilds each line item from an allowlist, so the REST route dropped these five before they got to PayPal.
  • collect_shipping_address goes out on every request — omit it and PayPal sets it back to true, so sending it once does not stick. The block attribute now defaults to true, which is what PayPal already does, so a new button is unchanged. api_response_to_attributes() maps it both ways: it only mapped the true case, so a payment collecting no address read as one that did.

Tax name field out

PayPal renders its own label to the buyer. Two live checkout pages, one with ZZ Custom VAT Label set and one with no name, both show Tax — the field changed nothing a customer saw, and blanking it turned tax collection off.

Three checks required that name. All three go, or the tax is lost between the form and PayPal:

Check Effect
use-paypal-resource.jstaxEnabled && taxName blank name, no taxes in the request
sanitize_line_items()! empty( $tax['name'] ) REST route stripped the tax on the way out
PayPal_Attribute_Mapper::attributes_to_api_request() dead code, no caller — left alone

name is optional on both tax types: 201 with none, echoed back without one. The name property comes off the route schema to match, and the admin detail card builds its label from what the tax actually has.

taxName stays as a block attribute and leaves RESOURCE_ATTRIBUTES — syncing it rewrote a merchant's stored name back to "Sales Tax" on every open.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  • Build packages/paypal-payments and load the branch on a test site.
  • Open a post with a PayPal Payment Buttons block, select the block, open the block settings sidebar.
  • In edit mode a Details panel sits above Style, holding every field ticked above. Each one is gone from the form on the canvas.
  • In edit mode the panels read Details, Product Options, Checkout Options, URL Redirect, Button Appearance, then Style, Display Format and PayPal Connection.
  • Switch the block toolbar to Preview: Details disappears, Style, Display Format and PayPal Connection stay.
  • Select the block in edit mode: Details is the only panel open. On a block with no button yet, Details and Product Options are open. Switch to Preview: Style alone.
  • On a block that already has a PayPal button, click Edit in the toolbar, change Product Name, click Save. The notice reads PayPal button updated successfully! Before this branch it read "An unexpected error occurred while communicating with PayPal. Please try again." while PayPal saved the edit anyway. Re-open the block and confirm the new name comes back from PayPal.
  • Delete a button from the block toolbar. Delete also answers 204, and still shows success.
  • In Checkout Options, turn on Collect tax. TAX TYPE and TAX NAME run the full width of the panel, flush with the toggles above and with no left border. Pick Use PayPal profile settings — it fits.
  • Leave the sidebar at its default width and open Style and Display Format together. Both button groups fill the column and share a left edge, and "Display Format" appears once.
  • In Details, all five labels read uppercase, including PRODUCT IMAGE (OPTIONAL), and DESCRIPTION (OPTIONAL) has the same gap above it as the others. Type in the description: the counter updates on its own line. Paste over 256 characters and click away — the help swaps to the length error and the box turns red.
  • In Product Options, turn on "Enable product options" and add a second option to a group. The option name and price fields run nearly the full width of the group card, with a single left border marking the nesting.
  • In Details, the Currency select reads USD $. Open it: 24 entries, each a code and its symbol, with CHF on its own. Pick JPY — Price switches to whole numbers (step 1, placeholder 1500) and the canvas preview shows ¥.
  • Insert a fresh block: the primary button reads Create New, disabled until Product Name and Price are filled. On one that already has a button, Edit in the toolbar shows Save. In either, click Button / Link / QR Code in Display Format — the format changes and the label stays put.

Update keeps what the form has no controls for

The block has no shipping, handling, discount or SKU controls yet, so make the payment through the REST route. On a post edit screen, in the browser console:

const made = await wp.apiFetch( { path: '/wpcom/v2/paypal/buttons', method: 'POST', data: {
  type: 'BUY_NOW', integration_mode: 'LINK', reusable: 'MULTIPLE',
  line_items: [ {
    name: 'update check', unit_amount: { currency_code: 'USD', value: '10.00' },
    product_id: 'SKU-1',
    shipping: [ { type: 'FLAT', value: '5.00', additional_unit_value: '2.00' } ],
    handling: [ { type: 'FLAT', value: '4.00' } ],
    discounts: [ { type: 'FLAT', value: '2.00' } ],
    collect_shipping_address: false,
  } ],
} } );
wp.data.dispatch( 'core/block-editor' ).insertBlock(
  wp.blocks.createBlock( 'jetpack/paypal-payment-buttons', {
    isApiManaged: true, resourceId: made.id, productName: 'update check',
    price: '10.00', currencyCode: 'USD',
  } )
);
made.id;
  • Select that block, click Edit in the toolbar, change Product Name, click Save.
  • Read the payment back with await wp.apiFetch( { path: '/wpcom/v2/paypal/buttons/<id>' } ). The new name is there, and so are product_id, shipping, handling, discounts and collect_shipping_address: false. Without this branch the five are gone and the address flag reads true.
  • Insert a fresh block, fill Product Name and Price, click Create New. Read it back: collect_shipping_address is true, same as before this branch.
  • Delete the payments you made: await wp.apiFetch( { path: '/wpcom/v2/paypal/buttons/<id>', method: 'DELETE' } ).

Tax with no name

  • Select a block with a saved button, click Edit, open Checkout Options, turn on Collect tax. The panel shows Tax type and Tax rate; Tax name is gone.
  • Set the rate to 8.25 and Save. Read it back with await wp.apiFetch( { path: '/wpcom/v2/paypal/buttons/<id>' } ): line_items[0].taxes is [ { type: 'PERCENTAGE', value: '8.25' } ]. Without this branch taxes is absent.
  • Switch Tax type to Use PayPal profile settings, Save, read back: [ { type: 'PREFERENCE', value: 'PROFILE' } ].
  • Turn Collect tax off, Save, read back: taxes is absent, and the product name, price and quantity are unchanged.

Screenshots

image

@tx2pnw tx2pnw self-assigned this Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the update/paypal-buttons-form-to-inspector branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack update/paypal-buttons-form-to-inspector

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Paypal Payment buttons plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@github-actions github-actions Bot added [Package] Paypal Payments [Status] In Progress [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. labels Sep 4, 2026
@jp-launch-control

jp-launch-control Bot commented Sep 4, 2026

Copy link
Copy Markdown

Code Coverage Summary

No summary data is available for parent commit f893ac3, so cannot calculate coverage changes. 😴

If that commit is a feature branch rather than a trunk commit, this is expected. Otherwise, this should be updated once coverage for f893ac3 is available.

Full summary · PHP report · JS report

@tx2pnw
tx2pnw force-pushed the update/paypal-buttons-form-to-inspector branch from 45abef7 to 5e3e314 Compare September 5, 2026 00:08
@tx2pnw
tx2pnw force-pushed the update/paypal-buttons-form-to-inspector branch from f965d87 to d8d93b6 Compare September 8, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant