|
Some checks are pending
Build and distribute / build-and-distribute (push) Waiting to run
CI / static-code-analysis-php (push) Waiting to run
CI / tests-unit-php (7.4) (push) Waiting to run
CI / tests-unit-php (8.0) (push) Waiting to run
CI / tests-unit-php (8.1) (push) Waiting to run
CI / tests-unit-php (8.2) (push) Waiting to run
CI / tests-unit-php (8.3) (push) Waiting to run
CI / tests-unit-php (8.4) (push) Waiting to run
CI / coding-standards-analysis-php (push) Waiting to run
* feat(abilities): scaffold Abilities_Registrar coordinator + Domain base
Phase I — adds the new ppcp-abilities module with the Abilities_Registrar
coordinator, the AbstractPpcpAbility Domain base, the
woocommerce_paypal_payments_abilities_enabled feature-flag gate
(default false), the WC 10.9 AbilitiesLoader presence check
(silent no-op on older WC versions), the can_manage_woocommerce()
capability helper that mirrors the wc/v3/wc_paypal/* REST controllers'
shared check_permission() resolution, and seven unit tests covering
the testable branches in the Brain Monkey environment. Concrete
abilities land in subsequent commits.
CATEGORY_SLUG is hardcoded to `woocommerce` — Woo Core (10.9+)
registers that category, so this registrar does not. Plugin ownership
lives in the ability namespace (woocommerce-paypal-payments/<name>).
Wires AbilitiesModule into modules.php so the Syde Modularity
container picks it up; per-ability registration stays gated behind
the feature flag.
Refs RSM-108
* feat(abilities): add woocommerce-paypal-payments/get-connection-status ability
Phase II (reference ability). Adds the smallest-safest read that backs
onto the existing wc/v3/wc_paypal/common/merchant route. Establishes
the Domain-class shape the remaining six reads will copy:
- One PHP file per ability under modules/ppcp-abilities/src/Domain/.
- Extends AbstractPpcpAbility, implements AbilityDefinition, lists
itself in Abilities_Registrar::ABILITY_CLASSES.
- Shape 2 (delegate to REST via the abstract base helper) — the
backing controller emits no telemetry and fires no hooks, so the
Shape 3 service-extraction overhead is not justified for this read.
Strips clientId + clientSecret from the merchant payload before
returning to the agent — the underlying $merchant_info_map exposes
both fields for the admin UI's manual-credentials flow, and an agent
echoing them would leak the OAuth credential pair.
Tests cover: namespace correctness (uses the plugin slug, not
`woocommerce/`), wiring (callbacks point at the Domain class + shared
helper), all three annotations, both projection opt-ins (show_in_rest
+ mcp.public), and the projection method's secret-redaction +
features-passthrough + WP_Error-on-envelope-failure contracts. The
delegate→REST→envelope happy path is exercised by the Phase V
integration harness against a real WC 10.9 install.
Adds a stub of \Automattic\WooCommerce\Abilities\AbilityDefinition to
tests/stubs/ so Domain classes autoload in the Brain Monkey unit-test
environment.
Refs RSM-108
* feat(abilities): register remaining 6 Phase III reads
Phase III — adds the rest of the MVP read surface as one coherent
batch. Every ability ships behind the same
woocommerce_paypal_payments_abilities_enabled feature flag (default
false), so a per-ability micro-commit split would add review noise
without isolating revert-able units.
Shape 2 (REST delegate via the abstract base helper) — the backing
controllers are zero-arg config reads that emit no telemetry and
fire no hooks:
- get-payment-methods → PaymentRestEndpoint::get_details
- get-settings → SettingsRestEndpoint::get_details
- get-webhook-status → WebhookSettingsEndpoint::get_webhooks
(issues a synchronous PayPal API call)
Shape 3 (direct service call) — these abilities expose operational
state that does not have an existing REST surface, so the ability
calls the plugin's container service directly:
- get-last-webhook-event → WebhookEventStorage::get_data()
- get-order-tracking → OrderTrackingEndpoint::list_tracking_information(int $wc_order_id)
(issues two synchronous PayPal API calls)
- get-paypal-order → OrderEndpointCached::order($paypal_id_or_wc_order)
(uses the cached endpoint to amortize cost
when invoked multiple times in a session)
Notable per-ability discipline:
- get-order-tracking declares wc_order_id as a JSON-schema-required
integer (minimum 1) AND re-validates in the execute callback.
- get-paypal-order accepts EITHER paypal_order_id OR wc_order_id;
"exactly one of" is enforced in the execute callback rather than
in the schema (no JSON Schema oneOf at this layer).
- get-paypal-order's description carries an explicit "may return
payer PII" notice so callers can apply downstream handling
appropriate to their context.
- get-last-webhook-event projects WebhookEventStorage's storage
payload into { received: bool, id, received_time, received_iso }
— the ISO timestamp is appended for agent ergonomics.
Service-resolved abilities (Shape 3) catch LogicException from the
PPCP container (raised when init runs before the plugin bootstraps)
and return a structured WP_Error rather than letting it bubble.
All 7 Domain classes are now listed in
Abilities_Registrar::ABILITY_CLASSES. Phase I + Phase II + this
commit bring the MVP surface to 7 reads.
Refs RSM-108
* chore(agents): note ability-registration audit when changing controller code
Phase VI.1 — drops the drift guard from the abilities-api-implement
playbook into AGENTS.md. Single-sentence reminder so an agent (human
or otherwise) modifying a backing controller / service knows to
audit the corresponding ability registration for shape, annotation,
or schema updates before merging.
Refs RSM-108
* fix(abilities): address round-1 review — PII redaction, format guards, refactor
Round-1 review feedback addressed (PR #4374 review-id 4293551129).
- GetPaypalOrder now redacts payer PII + per-purchase-unit shipping
addresses by default; callers opt in via include_payer_pii: true
when the calling context legitimately needs payer identity. Mirrors
the data-minimization pattern GetConnectionStatus already uses.
- GetPaypalOrder validates paypal_order_id against ^[A-Z0-9]{1,64}$
before passing it to OrderEndpointCached::order() (which
interpolates without rawurlencode); blocks path-traversal-style
payloads from altering the PayPal API URL path.
- GetPaypalOrder + GetOrderTracking no longer forward raw
PayPalApiException::getMessage() (which can include
information_link URLs) to the agent — generic message returned,
full exception logged via error_log() server-side.
- Three Shape-3 abilities now share resolve_service() on
AbstractPpcpAbility instead of each carrying an identical ~22-line
resolve_*() method. The shared helper also adds error_log() on the
bare-Throwable + unexpected-type paths so on-call has a server-side
trace when the container misbehaves.
- GetLastWebhookEvent description now accurately states the no-event
response shape ({ received: false }) rather than the previous
incorrect "null" claim.
- Renamed Abilities_Registrar -> AbilitiesRegistrar to match every
other module's PascalCase registrar naming
(WebhookRegistrar, InboxNoteRegistrar, TaskRegistrar).
- AbilitiesModule::run() now carries an explicit comment about why
the injected ContainerInterface is unused (static coordinator;
Shape-3 services resolve lazily at execute()-time via
PPCP::container()).
- AbilitiesRegistrar::$initialized now documents the hook-timing
requirement (must be invoked at-or-after plugins_loaded so WC
autoloading is warm; earlier hooks would let the class_exists
gate trip false and the guard would never re-arm).
- AbilitiesRegistrar feature-flag filter now carries a naming
rationale (runtime per-ability switch vs the dot-form module-level
gates in modules.php; intentionally different convention).
New tests:
- GetPaypalOrder paypal_order_id format guard (path traversal +
lowercase rejection)
- GetPaypalOrder wc_order_id with wc_get_order returning false
- GetPaypalOrder::project_order() PII redaction (default-strip,
shipping-address strip, opt-in passthrough)
Total: 42 unit tests / 141 assertions (was 36 / 125), full suite
1036 tests still green. PHPCS + PHPStan clean.
Pushback (will reply on the thread): kept
AbilitiesRegistrar::reset_initialized_for_testing() as public; the
@internal docblock + clearly-named method is the local convention,
and adding a PHPUNIT_RUNNING gate adds complexity for a theoretical
concern (the registrar is itself @internal and ships with
default-false abilities anyway).
Refs RSM-108
* fix(abilities): unblock npm run lint after AbilitiesRegistrar refactor
Round-2 review found two HIGH lint failures that the round-1 commit
(
|
||
|---|---|---|
| .ddev | ||
| .github | ||
| api | ||
| docs | ||
| lib | ||
| modules | ||
| src | ||
| stubs | ||
| tests | ||
| wordpress_org_assets | ||
| .distignore | ||
| .editorconfig | ||
| .env.example.e2e | ||
| .env.integration.example | ||
| .eslintignore | ||
| .eslintrc | ||
| .gitattributes | ||
| .gitignore | ||
| .npmrc | ||
| .wp-env.json | ||
| AGENTS.md | ||
| assets-compiler.json | ||
| babel.config.json | ||
| bootstrap.php | ||
| changelog.txt | ||
| CLAUDE.md | ||
| composer.json | ||
| composer.lock | ||
| E2E-TESTS.md | ||
| jsconfig.json | ||
| LICENSE | ||
| modules.php | ||
| package-lock.json | ||
| package.json | ||
| patchwork.json | ||
| phpcs.xml.dist | ||
| phpstan-baseline.neon | ||
| phpstan.neon | ||
| phpunit.xml.dist | ||
| playwright.config.ts | ||
| README.md | ||
| readme.txt | ||
| scoper.inc.php | ||
| tsconfig.json | ||
| uninstall.php | ||
| webpack.config.js | ||
| woocommerce-paypal-payments.php | ||
| wp-cli.yml | ||
WooCommerce PayPal Payments
PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
Features
- Multiple Payment Options: PayPal, credit/debit cards, Pay Later, digital wallets (Apple Pay, Google Pay), and localized payment methods
- Subscription Support: Supports WooCommerce Subscriptions with PayPal Vaulting and PayPal Subscriptions
- Customizable Experience: Flexible button placement and styling options
- Enhanced Security: PCI compliance, 3D Secure, and fraud protection tools
- Global Compliance: Meets international standards (PSD2, SCA)
Documentation
Visit our official documentation for detailed guides and setup instructions.
Dependencies
- PHP >= 7.4
- WordPress >= 6.5
- WooCommerce >= 9.6
Quick Installation
- Go to Plugins > Add New in your WordPress admin
- Search for "WooCommerce PayPal Payments"
- Click "Install Now" and then "Activate"
- Go to WooCommerce > Settings > Payments to configure PayPal Payments
Development
Setup using DDEV (recommended)
You can install WooCommerce PayPal Payments locally using the dev environment of your preference, or you can use the DDEV setup provided in this repository. Which includes WordPress, WooCommerce and all development tools.
To set up the DDEV environment, follow these steps:
- Install Docker and DDEV.
- Edit the configuration in the
.ddev/config.local.ymlfile if needed. - Run
$ ddev start && ddev orchestrateto setup and orchestrate the plugin, WooCommerce and WordPress (you can also use$ npm run ddev:setup) - Open https://woocommerce-paypal-payments.ddev.site
Use $ ddev reset for reinstallation (will destroy all site data).
You may also need $ ddev restart to apply the config changes.
Troubleshooting DDEV setup
vmnetd / port 443 error (failed to connect to /var/run/com.docker.vmnetd.sock):
Docker Desktop's privileged helper isn't running. Open Docker Desktop > Settings > General and toggle "Allow privileged port mapping", then restart Docker Desktop. Alternatively, use non-privileged ports:
$ ddev config global --router-http-port=8080 --router-https-port=8443
Mutagen can't find docker (unable to identify 'docker' command):
Docker Desktop may not symlink the CLI to /usr/local/bin. Either enable "Install Docker CLI in system PATH" in Docker Desktop settings, or create the symlink manually. Here's an example:
$ sudo ln -s /Applications/Docker.app/Contents/Resources/bin/docker /usr/local/bin/docker
Then run ddev mutagen reset && ddev restart.
Untrusted SSL / "Not Secure" in browser: Install mkcert's root CA so your system and browsers trust DDEV's certificates:
$ brew install mkcert nss
$ mkcert -install
$ ddev restart
After installing the CA, fully quit Chrome (Cmd+Q) and reopen it — Chrome caches certificate trust state and won't pick up the new CA until restarted.
Running tests and other tasks in the DDEV environment
Tests and code style:
$ ddev npm run test$ ddev npm run lint$ ddev npm run fix-lint$ ddev npm run lint-js$ npm run ddev:unit-tests:coverage
See package.json for other useful commands.
For debugging, see the DDEV docs.
Enable xdebug via $ ddev xdebug enable, and press Start Listening for PHP Debug Connections in PHPStorm.
After creating the server in the PHPStorm dialog, you need to set the local project path for the server plugin path.
Check this article for a detailed guide.
Setup in other environments
Install dependencies & build
$ composer install$ npm ci$ npm run build
Optionally, change the PAYPAL_INTEGRATION_DATE constant to gmdate( 'Y-m-d' ) to run the latest PayPal JavaScript SDK
Unit tests and code style
$ ./vendor/bin/phpunit$ ./vendor/bin/phpcs$ ./vendor/bin/phpstan$ npm run lint-js$ npm run test:unit-js- Ensure node version is18or above
Unit tests with Coverage
Run npm run ddev:unit-tests:coverage
This command generates a full test coverage report, available at the URL https://woocommerce-paypal-payments.ddev.site/coverage
Building a release package
If you want to build a release package, use the Build package (New) in GitHub Actions.
Currently, there is no script for building a proper release package locally, but you may try to run GHA locally via nektos/act.
Test account setup
You will need a PayPal sandbox merchant and customer accounts to configure the plugin and make test purchases with it.
For setting up test accounts follow these instructions.
Webhooks
For testing webhooks locally, follow these steps to set up ngrok:
-
Install ngrok.
-
If using DDEV, run our wrapper Bash script which will start
ddev shareand replace the URLs in the WordPress database:$ .ddev/bin/share -
For other environments, run
$ ngrok http -host-header=rewrite wc-pp.myhostand in your environment variables (accessible to the web server) add
NGROK_HOSTwith the host that you got fromngrok, likeabcd1234.ngrok.io. ngrok will be used only for the webhook listening URL. The URLs displayed on the WordPress pages, used in redirects, etc. will still remain local.
- Complete onboarding or resubscribe webhooks on the Webhooks Status page.
License
Contributing
All feedback / bug reports / pull requests are welcome.