Skip to content
Back to skills

Br Woo Routes

ASecurity

Expose WooCommerce orders, products, customers, and coupons with better-route 1.1 WooRouteRegistrar. Use for BetterRoute::wooRouteRegistrar, HPOS guards, actions, permissions, strict list/body validation, pagination meta, stable sorting, protected metadata, atomic idempotency, transactional order writes, product price rules, customer role/capability rules, coupon uniqueness, or Woo OpenAPI components.

  • 22 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added June 5, 2026
businessphpsqlapidatabase

Works with

  • api

Security analysis

A100/100

Scanned September 21, 2026

npx -y skills add Lonsdale201/wp-agent-skills --skill br-woo-routes --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Br Woo Routes?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Br Woo Routes
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lonsdale201-br-woo-routes/badge)](https://www.skillsdirectory.com/skills/lonsdale201-br-woo-routes)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: br-woo-routes
description: Expose WooCommerce orders, products, customers, and coupons with better-route 1.1 WooRouteRegistrar. Use for BetterRoute::wooRouteRegistrar, HPOS guards, actions, permissions, strict list/body validation, pagination meta, stable sorting, protected metadata, atomic idempotency, transactional order writes, product price rules, customer role/capability rules, coupon uniqueness, or Woo OpenAPI components.
metadata:
  wp-skills-author: "Soczó Kristóf"
  wp-skills-contact: "mailto:lonsdale201@hotmail.com"
  wp-skills-plugin: "better-route"
  wp-skills-plugin-version-tested: "1.1.1"
  wp-skills-php-min: "8.1"
  wp-skills-last-updated: "2026-09-21"
---

# better-route: WooCommerce routes

Register Woo routes during `rest_api_init` and retain the returned Router when contracts are needed.

```php
use BetterRoute\BetterRoute;

add_action('rest_api_init', static function (): void {
    $woo = BetterRoute::wooRouteRegistrar()->register('myapp/v1', [
        'basePath' => 'woo',
        'requireHpos' => true,
        'deleteMode' => 'trash',
        'actions' => [
            'customers' => ['list', 'get'],
            'coupons' => [],
        ],
        'permissions' => [
            'orders.list' => 'manage_woocommerce',
            'orders.create' => 'manage_woocommerce',
        ],
    ]);
});
```

Routes cover orders, products, customers, and coupons under `/wp-json/myapp/v1/woo` by default.

## Actions and permissions

Each resource supports `list`, `get`, `create`, `update`, and `delete`; update registers both PUT and PATCH.

- Omit `actions.<resource>` for full CRUD.
- Pass `[]` to disable that resource.
- Invalid action names or a non-array value throw.

Permission keys use `<resource>.<action>`. A value may be bool, capability string, any-of capability list, or callable receiving the request and optionally the registrar. Unrecognized/empty rules deny. Defaults are `manage_woocommerce`.

Customer writes also enforce native user capabilities inside the service:

- create: `create_users`;
- update: `edit_user` for the target;
- delete: `delete_user` for the target.

Customer list/get expose users with the `customer` role only; a general WordPress user directory is intentionally not provided.

## Lists

```http
GET /wp-json/myapp/v1/woo/orders?status=processing&sort=-date_created&page=1&per_page=50&fields=id,total,status
```

Lists return pagination in the JSON envelope:

```json
{"data": [], "meta": {"page": 1, "perPage": 50, "total": 0}}
```

Do not expect `X-WP-Total` or `X-WP-TotalPages`; Better Route Woo pagination is in `meta`.

`per_page > maxPerPage` returns `400 validation_failed`; it is not clamped. `sort=-field` means DESC. Services add an ID tie-breaker so equal primary sort values paginate deterministically.

Strict parsers reject unknown parameters but accept WordPress globals `_locale`, `_fields`, `_embed`, `_envelope`, and `_jsonp`, including `wp.apiFetch`'s `_locale=user`.

Resource filters/sorts differ:

- orders: status, customer_id, search; stable order/date/total options supported by parser;
- products: status, type, sku, search, stock_status; derived price sorting is not supported;
- customers: role, email, search;
- coupons: code, search.

Use each parser's allowlist rather than forwarding arbitrary Woo query vars.

## Strict write payloads in 1.1

All services reject unknown top-level fields and enforce field types before setters/save.

Orders:

- validate the complete payload before persistence;
- reject unknown billing/shipping/line-item keys;
- require `product_id` for line items and validate product/variation existence and relationship;
- require positive finite line quantities supported by Woo's stock normalizer, and non-negative finite totals;
- refuse line-item replacement after stock reduction with `409 woo_line_items_locked`;
- run create/update inside `wc_transaction_query('start'/'commit'/'rollback')`.

Products:

- treat `price` as read-only;
- write `regular_price` and `sale_price` instead;
- validate all fields before applying setters/save.

Customers:

- require `email` on create;
- reject `username` on update because WordPress usernames are immutable through this API;
- reject unknown address keys and non-string address values;
- omit expensive `orders_count` and `total_spent` from list defaults to avoid N+1 work; request them explicitly when needed.

Coupons:

- require `code` on create;
- strictly validate monetary, boolean, list, date, and metadata fields;
- enforce normalized code uniqueness on create and update under a MySQL named lock;
- exclude the current coupon ID when checking an update; conflicts return `409 coupon_exists`.

Money response fields are decimal strings, not JSON floats.

## 1.1.1 order lifecycle and quantities

- Initialize payment gateways before writes; apply items/addresses and calculate taxes/totals before staging the requested status. Status hooks see final totals.
- Treat billing/shipping-only updates as financially significant: they recalculate totals even on paid orders.
- Save before payment completion. On update, `set_paid: true` calls `payment_complete()` only when the order still needs payment. Repeated paid updates do not repeat the event. An already-paid status with `set_paid` is not a guarantee of that event or evidence of external capture.
- Do not assume database rollback undoes emails, webhooks or external hook effects.
- Accept finite numeric quantities (including numeric strings). Order lines must be positive; product stock accepts negative values and null. Reject booleans, non-numeric/non-finite values, and any quantity that `wc_stock_amount()` would change, before persistence.
- Keep fractional Woo stock configuration active for later reads as well as writes. Woo normalizes quantities on load; Better Route does not recover old fractions by bypassing its data store.
- Use OpenAPI `number` for line quantities, `exclusiveMinimum: 0` on input, and `number` or `null` for stock.

The registrar is administrative CRUD, not a Store API cart/checkout adapter. It adds no storefront or refund endpoints in 1.1.1. Runtime changes were checked with WooCommerce 11.1.1 on both HPOS and legacy storage; `requireHpos` remains enabled by default.

## Metadata

Use the actual helper APIs:

```php
$meta = MetaDataHelper::normalizeIncoming($payload['meta_data'] ?? null);
MetaDataHelper::applyToTarget($object, $meta);
$serialized = MetaDataHelper::serialize($object->get_meta_data());
```

Incoming metadata may be a key/value map or a list of `{key,value}` entries. Underscore-prefixed protected keys are rejected on write and omitted on serialization by default. Opt in with `allowProtected/includeProtected` only on tightly protected internal code paths.

## Atomic idempotency

```php
'idempotency' => [
    'enabled' => true,
    'requireKey' => true,
    'ttlSeconds' => 86400,
    'resources' => [
        'orders' => true,
        'products' => true,
        'customers' => true,
        'coupons' => true,
    ],
],
```

Idempotency defaults to disabled and `requireKey` defaults to false; enable both explicitly for protected retry-safe writes. 1.1 uses `AtomicIdempotencyMiddleware` and requires a custom store to implement `AtomicIdempotencyStoreInterface`. Under WordPress, the default is a lease-aware wpdb store whose schema is installed/migrated once per version option; failure is surfaced rather than silently falling back.

The current 1.1 registrar attaches idempotency to create and update routes. DELETE routes are not wrapped by the registrar's idempotency configuration; add a custom raw route/middleware if idempotent delete replay is a requirement.

Before updating existing idempotent writers to 1.1.1, follow `br-install-and-migrate`: default namespace/URL keys and fingerprints change without translating legacy records.

## HPOS

`requireHpos` defaults to true and gates order routes only. When HPOS is unavailable, order routes return `503 hpos_required`; products/customers/coupons are not HPOS data stores.

The host plugin must also declare compatibility from its main file:

```php
BetterRoute\Integration\Woo\HposGuard::declareCompatibility(__FILE__);
```

The runtime guard does not replace Woo's feature compatibility declaration.

## OpenAPI

Use the returned Router's contracts and `BetterRoute::wooOpenApiComponents()`. In 1.1 input schemas match strict runtime behavior: customer create requires email, coupon create requires code, product input excludes derived price, order line input requires product ID, and nested addresses disallow additional properties.

## Review checklist

- Set `deleteMode` explicitly; default `force` is destructive.
- Keep HPOS required for production order APIs and declare host compatibility.
- Verify action omission versus explicit `[]`.
- Test customer role plus native capabilities.
- Assert oversized `per_page` is 400 and read totals from JSON `meta`.
- Test unknown nested keys and exact scalar types for every write entity.
- Concurrency-test duplicate idempotent create/update and duplicate coupon codes.
- Request expensive customer aggregate fields only when necessary.

## Related skills

- Use `br-atomic-idempotency` for reservation semantics.
- Use `br-openapi` for document generation.
- Use WooCommerce HPOS skills for migration/operational setup.

## References

- Verified source paths:
  - `src/Integration/Woo/WooRouteRegistrar.php`
  - `src/Integration/Woo/WooOrderService.php`
  - `src/Integration/Woo/WooProductService.php`
  - `src/Integration/Woo/WooCustomerService.php`
  - `src/Integration/Woo/WooCouponService.php`
  - `src/Integration/Woo/WooOpenApiComponents.php`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.