@sadrmanesh/commerce-agent-skills 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README.md +141 -2
- package/bin/commerce-agent-skills.mjs +368 -0
- package/package.json +52 -4
- package/rules/b2b-pricing.md +11 -0
- package/rules/catalog-import-and-migration.md +11 -0
- package/rules/checkout-ux-and-conversion.md +12 -0
- package/rules/conversion-event-tracking.md +11 -0
- package/rules/ecommerce-seo-structured-data.md +11 -0
- package/rules/fraud-and-risk-basics.md +11 -0
- package/rules/google-merchant-product-feeds.md +11 -0
- package/rules/headless-nextjs-storefront.md +10 -0
- package/rules/inventory-and-multi-warehouse.md +10 -0
- package/rules/iranian-payment-gateways.md +10 -0
- package/rules/marketplace-search-and-ranking.md +10 -0
- package/rules/mcp-store-tooling.md +11 -0
- package/rules/multi-vendor-payouts.md +11 -0
- package/rules/order-and-webhook-reliability.md +12 -0
- package/rules/payment-gateway-integration.md +12 -0
- package/rules/product-catalog-modeling.md +11 -0
- package/rules/product-description-guardrails.md +10 -0
- package/rules/returns-and-refunds.md +10 -0
- package/rules/rtl-and-persian-localization.md +11 -0
- package/rules/shopify-app-development.md +10 -0
- package/rules/storefront-accessibility.md +11 -0
- package/rules/storefront-performance.md +10 -0
- package/rules/stripe-payments.md +10 -0
- package/rules/subscription-billing.md +10 -0
- package/rules/tax-and-shipping.md +10 -0
- package/rules/vector-product-search.md +10 -0
- package/rules/woocommerce-plugin-development.md +10 -0
- package/rules/woocommerce-rest-api.md +10 -0
- package/rules/wordpress-plugin-security.md +10 -0
- package/scripts/lib/frontmatter.mjs +218 -0
- package/skills/b2b-pricing/SKILL.md +55 -0
- package/skills/b2b-pricing/evals/examples/net-terms.md +3 -0
- package/skills/b2b-pricing/evals/examples/tiered-wholesale.md +3 -0
- package/skills/b2b-pricing/evals/tasks.json +19 -0
- package/skills/b2b-pricing/references/price-lists.md +46 -0
- package/skills/b2b-pricing/scripts/resolve-price.mjs +93 -0
- package/skills/catalog-import-and-migration/SKILL.md +58 -0
- package/skills/catalog-import-and-migration/evals/examples/platform-migration-urls.md +3 -0
- package/skills/catalog-import-and-migration/evals/examples/rerunnable-import.md +3 -0
- package/skills/catalog-import-and-migration/evals/tasks.json +20 -0
- package/skills/catalog-import-and-migration/references/field-mapping.md +43 -0
- package/skills/catalog-import-and-migration/scripts/check-import-rows.mjs +75 -0
- package/skills/catalog-order.txt +29 -0
- package/skills/checkout-ux-and-conversion/SKILL.md +86 -0
- package/skills/checkout-ux-and-conversion/evals/examples/guest-total.md +3 -0
- package/skills/checkout-ux-and-conversion/evals/examples/single-submit.md +3 -0
- package/skills/checkout-ux-and-conversion/evals/tasks.json +30 -0
- package/skills/checkout-ux-and-conversion/references/checkout-review.md +41 -0
- package/skills/conversion-event-tracking/SKILL.md +64 -0
- package/skills/conversion-event-tracking/evals/examples/purchase-once.md +3 -0
- package/skills/conversion-event-tracking/evals/examples/variant-item-id.md +3 -0
- package/skills/conversion-event-tracking/evals/tasks.json +30 -0
- package/skills/conversion-event-tracking/references/events.md +36 -0
- package/skills/conversion-event-tracking/scripts/check-event.mjs +79 -0
- package/skills/ecommerce-seo-structured-data/SKILL.md +84 -0
- package/skills/ecommerce-seo-structured-data/assets/example-product.jsonld +15 -0
- package/skills/ecommerce-seo-structured-data/evals/examples/extend-woocommerce-schema.md +8 -0
- package/skills/ecommerce-seo-structured-data/evals/examples/variant-offer.md +7 -0
- package/skills/ecommerce-seo-structured-data/evals/tasks.json +29 -0
- package/skills/ecommerce-seo-structured-data/references/product-jsonld.md +83 -0
- package/skills/ecommerce-seo-structured-data/scripts/check-product-jsonld.mjs +211 -0
- package/skills/fraud-and-risk-basics/SKILL.md +66 -0
- package/skills/fraud-and-risk-basics/evals/examples/review-queue.md +3 -0
- package/skills/fraud-and-risk-basics/evals/examples/wait-for-3ds.md +3 -0
- package/skills/fraud-and-risk-basics/evals/tasks.json +30 -0
- package/skills/google-merchant-product-feeds/SKILL.md +70 -0
- package/skills/google-merchant-product-feeds/assets/example-item.json +11 -0
- package/skills/google-merchant-product-feeds/evals/examples/generated-title.md +3 -0
- package/skills/google-merchant-product-feeds/evals/examples/stable-variant-id.md +3 -0
- package/skills/google-merchant-product-feeds/evals/tasks.json +30 -0
- package/skills/google-merchant-product-feeds/references/attributes.md +39 -0
- package/skills/google-merchant-product-feeds/scripts/check-feed-item.mjs +92 -0
- package/skills/headless-nextjs-storefront/SKILL.md +69 -0
- package/skills/headless-nextjs-storefront/evals/examples/revalidate-stock.md +7 -0
- package/skills/headless-nextjs-storefront/evals/examples/server-price.md +3 -0
- package/skills/headless-nextjs-storefront/evals/tasks.json +30 -0
- package/skills/headless-nextjs-storefront/references/cache-and-cart.md +39 -0
- package/skills/inventory-and-multi-warehouse/SKILL.md +61 -0
- package/skills/inventory-and-multi-warehouse/evals/examples/reserve-last-unit.md +3 -0
- package/skills/inventory-and-multi-warehouse/evals/examples/split-order.md +3 -0
- package/skills/inventory-and-multi-warehouse/evals/tasks.json +20 -0
- package/skills/inventory-and-multi-warehouse/references/platforms.md +40 -0
- package/skills/inventory-and-multi-warehouse/scripts/allocate-stock.mjs +72 -0
- package/skills/iranian-payment-gateways/SKILL.md +64 -0
- package/skills/iranian-payment-gateways/evals/examples/toman-store.md +3 -0
- package/skills/iranian-payment-gateways/evals/examples/zarinpal-callback.md +3 -0
- package/skills/iranian-payment-gateways/evals/tasks.json +20 -0
- package/skills/iranian-payment-gateways/references/zarinpal-v4.md +44 -0
- package/skills/iranian-payment-gateways/scripts/check-verify.mjs +62 -0
- package/skills/marketplace-search-and-ranking/SKILL.md +93 -0
- package/skills/marketplace-search-and-ranking/evals/examples/index-variant.md +3 -0
- package/skills/marketplace-search-and-ranking/evals/examples/normalize-query.md +7 -0
- package/skills/marketplace-search-and-ranking/evals/tasks.json +29 -0
- package/skills/marketplace-search-and-ranking/references/document-and-ranking.md +56 -0
- package/skills/marketplace-search-and-ranking/scripts/normalize-search-text.mjs +40 -0
- package/skills/mcp-store-tooling/SKILL.md +49 -0
- package/skills/mcp-store-tooling/evals/examples/refund-tool-design.md +3 -0
- package/skills/mcp-store-tooling/evals/examples/remote-server-auth.md +3 -0
- package/skills/mcp-store-tooling/evals/tasks.json +20 -0
- package/skills/mcp-store-tooling/references/mcp-notes.md +67 -0
- package/skills/mcp-store-tooling/scripts/check-tool-definitions.mjs +69 -0
- package/skills/multi-vendor-payouts/SKILL.md +71 -0
- package/skills/multi-vendor-payouts/evals/examples/retry-payout.md +3 -0
- package/skills/multi-vendor-payouts/evals/examples/split-paid-order.md +3 -0
- package/skills/multi-vendor-payouts/evals/tasks.json +30 -0
- package/skills/multi-vendor-payouts/references/ledger.md +35 -0
- package/skills/order-and-webhook-reliability/SKILL.md +99 -0
- package/skills/order-and-webhook-reliability/evals/examples/dedupe-resource-version.md +3 -0
- package/skills/order-and-webhook-reliability/evals/examples/verify-raw-body.md +3 -0
- package/skills/order-and-webhook-reliability/evals/tasks.json +29 -0
- package/skills/order-and-webhook-reliability/references/woocommerce-webhooks.md +58 -0
- package/skills/order-and-webhook-reliability/scripts/verify-hmac-signature.mjs +78 -0
- package/skills/payment-gateway-integration/SKILL.md +90 -0
- package/skills/payment-gateway-integration/evals/examples/confirmed-capture.md +9 -0
- package/skills/payment-gateway-integration/evals/examples/replayed-webhook.md +3 -0
- package/skills/payment-gateway-integration/evals/tasks.json +30 -0
- package/skills/payment-gateway-integration/references/woocommerce-gateway.md +67 -0
- package/skills/product-catalog-modeling/SKILL.md +91 -0
- package/skills/product-catalog-modeling/assets/example-catalog.json +45 -0
- package/skills/product-catalog-modeling/evals/examples/create-variation.md +10 -0
- package/skills/product-catalog-modeling/evals/examples/sync-parent.md +7 -0
- package/skills/product-catalog-modeling/evals/tasks.json +29 -0
- package/skills/product-catalog-modeling/references/variation-model.md +71 -0
- package/skills/product-catalog-modeling/scripts/validate-catalog.mjs +234 -0
- package/skills/product-description-guardrails/SKILL.md +84 -0
- package/skills/product-description-guardrails/evals/examples/facts-only.md +3 -0
- package/skills/product-description-guardrails/evals/examples/label-generated.md +3 -0
- package/skills/product-description-guardrails/evals/tasks.json +30 -0
- package/skills/product-description-guardrails/references/claims.md +24 -0
- package/skills/product-description-guardrails/scripts/check-description.mjs +61 -0
- package/skills/returns-and-refunds/SKILL.md +55 -0
- package/skills/returns-and-refunds/evals/examples/refund-after-inspection.md +3 -0
- package/skills/returns-and-refunds/evals/examples/woocommerce-partial-refund.md +3 -0
- package/skills/returns-and-refunds/evals/tasks.json +20 -0
- package/skills/returns-and-refunds/references/rma-states.md +37 -0
- package/skills/rtl-and-persian-localization/SKILL.md +81 -0
- package/skills/rtl-and-persian-localization/evals/examples/document-direction.md +7 -0
- package/skills/rtl-and-persian-localization/evals/examples/rial-display.md +7 -0
- package/skills/rtl-and-persian-localization/evals/tasks.json +30 -0
- package/skills/rtl-and-persian-localization/references/persian-text.md +51 -0
- package/skills/rtl-and-persian-localization/scripts/normalize-persian.mjs +62 -0
- package/skills/shopify-app-development/SKILL.md +73 -0
- package/skills/shopify-app-development/evals/examples/dedupe-delivery.md +3 -0
- package/skills/shopify-app-development/evals/examples/verify-webhook.md +3 -0
- package/skills/shopify-app-development/evals/tasks.json +29 -0
- package/skills/shopify-app-development/references/auth-and-webhooks.md +44 -0
- package/skills/storefront-accessibility/SKILL.md +59 -0
- package/skills/storefront-accessibility/evals/examples/add-to-cart-announcement.md +3 -0
- package/skills/storefront-accessibility/evals/examples/checkout-errors.md +3 -0
- package/skills/storefront-accessibility/evals/tasks.json +20 -0
- package/skills/storefront-accessibility/references/wcag-checklist.md +41 -0
- package/skills/storefront-performance/SKILL.md +59 -0
- package/skills/storefront-performance/evals/examples/cache-without-leaking-carts.md +3 -0
- package/skills/storefront-performance/evals/examples/slow-product-lcp.md +3 -0
- package/skills/storefront-performance/evals/tasks.json +20 -0
- package/skills/storefront-performance/references/web-vitals.md +55 -0
- package/skills/storefront-performance/scripts/check-web-vitals.mjs +49 -0
- package/skills/stripe-payments/SKILL.md +72 -0
- package/skills/stripe-payments/evals/examples/checkout-session-delayed.md +3 -0
- package/skills/stripe-payments/evals/examples/mark-paid-from-webhook.md +5 -0
- package/skills/stripe-payments/evals/examples/partial-refund.md +3 -0
- package/skills/stripe-payments/evals/tasks.json +27 -0
- package/skills/stripe-payments/references/stripe-objects.md +54 -0
- package/skills/stripe-payments/scripts/verify-stripe-signature.mjs +62 -0
- package/skills/subscription-billing/SKILL.md +76 -0
- package/skills/subscription-billing/evals/examples/cancel-at-period-end.md +3 -0
- package/skills/subscription-billing/evals/examples/one-renewal.md +3 -0
- package/skills/subscription-billing/evals/tasks.json +30 -0
- package/skills/subscription-billing/references/states.md +27 -0
- package/skills/tax-and-shipping/SKILL.md +61 -0
- package/skills/tax-and-shipping/evals/examples/store-tax-lines.md +3 -0
- package/skills/tax-and-shipping/evals/examples/unknown-zone.md +3 -0
- package/skills/tax-and-shipping/evals/tasks.json +30 -0
- package/skills/tax-and-shipping/references/woocommerce.md +22 -0
- package/skills/vector-product-search/SKILL.md +66 -0
- package/skills/vector-product-search/evals/examples/reembed-on-title.md +3 -0
- package/skills/vector-product-search/evals/examples/sku-before-vector.md +3 -0
- package/skills/vector-product-search/evals/tasks.json +30 -0
- package/skills/vector-product-search/references/index.md +40 -0
- package/skills/woocommerce-plugin-development/SKILL.md +84 -0
- package/skills/woocommerce-plugin-development/assets/main-plugin-file.php +28 -0
- package/skills/woocommerce-plugin-development/evals/examples/declare-hpos.md +9 -0
- package/skills/woocommerce-plugin-development/evals/examples/save-order-meta.md +9 -0
- package/skills/woocommerce-plugin-development/evals/tasks.json +32 -0
- package/skills/woocommerce-plugin-development/references/hooks.md +46 -0
- package/skills/woocommerce-plugin-development/references/hpos.md +84 -0
- package/skills/woocommerce-rest-api/SKILL.md +90 -0
- package/skills/woocommerce-rest-api/evals/examples/https-basic-auth.md +8 -0
- package/skills/woocommerce-rest-api/evals/examples/idempotent-order-create.md +7 -0
- package/skills/woocommerce-rest-api/evals/tasks.json +30 -0
- package/skills/woocommerce-rest-api/references/endpoints.md +66 -0
- package/skills/wordpress-plugin-security/SKILL.md +121 -0
- package/skills/wordpress-plugin-security/evals/examples/admin-form-nonce.md +9 -0
- package/skills/wordpress-plugin-security/evals/examples/private-rest-route.md +9 -0
- package/skills/wordpress-plugin-security/evals/tasks.json +31 -0
- package/skills/wordpress-plugin-security/references/review-checklist.md +31 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: woocommerce-rest-api
|
|
3
|
+
description: "Call the WooCommerce REST API at wc/v3, including authentication, pagination, batch writes, and webhook registration, and keep it separate from the Store API. Use when an external system integrates with a WooCommerce store over HTTP."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Naser Sadrmanesh
|
|
7
|
+
version: "0.3.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# WooCommerce REST API
|
|
11
|
+
|
|
12
|
+
Use this skill for a server-side client of a WooCommerce store. The browser checkout uses a different API. Payment notification handling belongs to the webhook reliability skill.
|
|
13
|
+
|
|
14
|
+
## Which API
|
|
15
|
+
|
|
16
|
+
| API | Namespace | Who calls it |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| WooCommerce REST API | `/wp-json/wc/v3/` | Your server, with a consumer key and secret. |
|
|
19
|
+
| Store API | `/wp-json/wc/store/v1/` | The storefront, with a cart token and nonce. |
|
|
20
|
+
| Legacy REST API | `/wc-api/v3/` | Old clients only. Do not start new work here. |
|
|
21
|
+
|
|
22
|
+
The current REST API requires WooCommerce 3.5+, WordPress 4.4+, and pretty permalinks. Default permalinks do not serve these routes.
|
|
23
|
+
|
|
24
|
+
Never put a consumer secret in browser JavaScript, a mobile binary, or a Store API request.
|
|
25
|
+
|
|
26
|
+
## Authentication
|
|
27
|
+
|
|
28
|
+
Create a key at **WooCommerce > Settings > Advanced > REST API**. The key inherits the WordPress user you bind it to, and the permission you select: Read, Write, or Read/Write. Deleting that user disables the key. The consumer secret is shown once.
|
|
29
|
+
|
|
30
|
+
Over HTTPS, send HTTP Basic authentication with the consumer key as the username and the consumer secret as the password.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl --user "$WC_CONSUMER_KEY:$WC_CONSUMER_SECRET" \
|
|
34
|
+
"https://shop.example/wp-json/wc/v3/orders?per_page=20"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Some FastCGI hosts drop the `Authorization` header and WooCommerce then reports that the consumer key is missing. Only then, and only over HTTPS, send `consumer_key` and `consumer_secret` as query parameters. Query secrets land in access logs and caches. Prefer fixing the host header.
|
|
38
|
+
|
|
39
|
+
Over plain HTTP, WooCommerce expects one-legged OAuth 1.0a. For a new integration, use HTTPS instead of adding an OAuth client.
|
|
40
|
+
|
|
41
|
+
Store the secret in the server environment or a secret manager. Do not commit it.
|
|
42
|
+
|
|
43
|
+
## Reads
|
|
44
|
+
|
|
45
|
+
- Follow pages with `page` and `per_page`. `per_page` above 100 is rejected. Read `X-WP-Total` and `X-WP-TotalPages`.
|
|
46
|
+
- Request only the fields you need when the endpoint supports `_fields`.
|
|
47
|
+
- Compare instants with `date_created_gmt` and `date_modified_gmt`. The unsuffixed fields are in the site timezone.
|
|
48
|
+
- Product variations are a child resource: `/wp-json/wc/v3/products/<id>/variations`.
|
|
49
|
+
- Error bodies use the WordPress shape `{ "code", "message", "data": { "status" } }`. Branch on `code` and HTTP status, not on the message string.
|
|
50
|
+
|
|
51
|
+
Endpoint groups and the batch body are in [the endpoint notes](references/endpoints.md).
|
|
52
|
+
|
|
53
|
+
## Writes
|
|
54
|
+
|
|
55
|
+
The REST API does not provide a general idempotency key. A retried `POST /orders` can create a second order.
|
|
56
|
+
|
|
57
|
+
1. Put your own key in meta, for example `meta_data: [{ "key": "_idempotency_key", "value": "<key>" }]`.
|
|
58
|
+
2. Before creating, search orders by that meta. Treat a hit as the original result.
|
|
59
|
+
3. Meta search is not a unique index. Two concurrent creates can still both miss. For payments, dedupe on the provider transaction id as well. See the order and webhook skill.
|
|
60
|
+
|
|
61
|
+
Batch routes (`POST /products/batch`, `/orders/batch`, and the customer and coupon equivalents) accept `create`, `update`, and `delete`. The batch is not one database transaction. Read each item result. A 200 response can still contain per-item errors.
|
|
62
|
+
|
|
63
|
+
Update orders and products with the resource id. Do not match a later update on SKU alone unless you have already resolved the SKU to one id. SKUs can change.
|
|
64
|
+
|
|
65
|
+
## Webhooks registered over REST
|
|
66
|
+
|
|
67
|
+
`POST /wp-json/wc/v3/webhooks` with `name`, `topic`, `delivery_url`, and `secret`. The secret is write-only on later reads. If you omit it, WooCommerce falls back to the API user's consumer secret. Set an explicit secret.
|
|
68
|
+
|
|
69
|
+
The first save of an active webhook delivers a ping, not a full order or product. Acknowledge that ping with a 2xx response and do not create an order from it.
|
|
70
|
+
|
|
71
|
+
Topic names look like `order.updated` and `product.created`. The delivered resource body matches the REST representation. Verify `X-WC-Webhook-Signature` against the raw body before trusting it. The signature procedure is in the order and webhook skill.
|
|
72
|
+
|
|
73
|
+
## Gotchas
|
|
74
|
+
|
|
75
|
+
- A Read key cannot create products. A Write key cannot list orders. Check the key permission and the user's capability when you get `401` or `403`.
|
|
76
|
+
- Order status names in the API omit the `wc-` prefix (`processing`, not `wc-processing`).
|
|
77
|
+
- Prices are strings in major currency units, such as `"19.00"`. Do not send a float.
|
|
78
|
+
- HPOS does not change the REST shape. Clients should keep using this API rather than reading the database.
|
|
79
|
+
- Webhook delivery that returns `301` or `302` can be counted as success while `WC_Webhook::deliver()` sends the body with redirects disabled. Serve the JSON handler on the exact `delivery_url`.
|
|
80
|
+
|
|
81
|
+
## Verification
|
|
82
|
+
|
|
83
|
+
- A request with the wrong secret returns 401 and creates nothing.
|
|
84
|
+
- Listing orders walks every `X-WP-TotalPages` page without duplicating or skipping ids.
|
|
85
|
+
- Repeating the same create with the same idempotency key returns one order.
|
|
86
|
+
- A webhook ping does not create an order. A signed `order.updated` delivery updates the matching local record.
|
|
87
|
+
|
|
88
|
+
## Evaluation
|
|
89
|
+
|
|
90
|
+
Worked examples and the checks they must satisfy are in [evals/tasks.json](evals/tasks.json).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tasks": [
|
|
3
|
+
{
|
|
4
|
+
"id": "https-basic-auth",
|
|
5
|
+
"task": "Authenticate a server-side WooCommerce REST client over HTTPS without putting the secret in the browser.",
|
|
6
|
+
"expects": {
|
|
7
|
+
"must_include": [
|
|
8
|
+
"/wp-json/wc/v3/",
|
|
9
|
+
"Basic"
|
|
10
|
+
],
|
|
11
|
+
"must_not_include": [
|
|
12
|
+
"wc-api/v3"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "idempotent-order-create",
|
|
18
|
+
"task": "Create an order through the REST API so a retried POST does not create a second order.",
|
|
19
|
+
"expects": {
|
|
20
|
+
"must_include": [
|
|
21
|
+
"meta_data",
|
|
22
|
+
"_idempotency_key"
|
|
23
|
+
],
|
|
24
|
+
"must_not_include": [
|
|
25
|
+
"consumer_secret="
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# WooCommerce REST API endpoints
|
|
2
|
+
|
|
3
|
+
Base path: `/wp-json/wc/v3/`. All routes below are relative to that base. Authenticate every call as described in `SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Resources
|
|
6
|
+
|
|
7
|
+
| Path | Typical use |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `products` | Create and update the parent product. |
|
|
10
|
+
| `products/<id>/variations` | Create and update purchasable variants. |
|
|
11
|
+
| `products/attributes` | Global attributes. Taxonomy name is `pa_` plus the slug. |
|
|
12
|
+
| `products/categories` | Product categories. |
|
|
13
|
+
| `products/<id>/reviews` | Product reviews. |
|
|
14
|
+
| `orders` | Order header, line items, shipping lines, fee lines, and coupons. |
|
|
15
|
+
| `orders/<id>/notes` | Customer or private notes. `customer_note: true` emails the customer. |
|
|
16
|
+
| `orders/<id>/refunds` | Create a refund. This is the REST equivalent of `wc_create_refund()`. |
|
|
17
|
+
| `customers` | WordPress users who are customers. Guest orders are not customers until an account exists. |
|
|
18
|
+
| `coupons` | Coupon definitions. Applying a coupon still happens in checkout or by editing the order. |
|
|
19
|
+
| `webhooks` | Register, pause, or delete a webhook. `secret` is write-only. |
|
|
20
|
+
| `taxes`, `shipping/zones`, `payment_gateways`, `settings` | Store configuration. Prefer the admin UI unless you are provisioning stores. |
|
|
21
|
+
| `system_status` | Environment diagnostic. Do not require it on the hot path. |
|
|
22
|
+
| `reports/sales` | Aggregates. For large extracts, page through `orders` instead of assuming one report call is complete. |
|
|
23
|
+
|
|
24
|
+
`<id>` is the integer resource id.
|
|
25
|
+
|
|
26
|
+
## Batch
|
|
27
|
+
|
|
28
|
+
`POST products/batch`, `orders/batch`, `customers/batch`, and `coupons/batch`.
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"create": [],
|
|
33
|
+
"update": [{ "id": 10, "status": "draft" }],
|
|
34
|
+
"delete": [11]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Inspect `create`, `update`, and `delete` in the response. An item error does not roll back its siblings.
|
|
39
|
+
|
|
40
|
+
## Pagination and filters
|
|
41
|
+
|
|
42
|
+
- `page` starts at 1. `per_page` defaults to 10 and cannot exceed 100.
|
|
43
|
+
- Order list filters include `status`, `customer`, `product`, `after`, `before`, `modified_after`, and `modified_before`. Pass the date filters as ISO-8601 timestamps.
|
|
44
|
+
- Use `modified_after` for an incremental sync. A record can change while you are paging. Keep an overlap and dedupe by id plus `date_modified_gmt`. If the installed WooCommerce rejects `modified_after`, fall back to `after` and `before`.
|
|
45
|
+
|
|
46
|
+
## Status values
|
|
47
|
+
|
|
48
|
+
Order statuses used by core: `pending`, `processing`, `on-hold`, `completed`, `cancelled`, `refunded`, `failed`, and `checkout-draft`. Custom statuses registered with `woocommerce_register_shop_order_post_statuses` also appear. Do not send the `wc-` internal prefix.
|
|
49
|
+
|
|
50
|
+
Product `status` uses WordPress post statuses such as `publish`, `draft`, and `private`. Catalog visibility is `catalog_visibility` (`visible`, `catalog`, `search`, `hidden`), which is not the same field.
|
|
51
|
+
|
|
52
|
+
## Webhook topics
|
|
53
|
+
|
|
54
|
+
Common topics: `order.created`, `order.updated`, `order.deleted`, `product.created`, `product.updated`, `product.deleted`, `customer.created`, `customer.updated`, `coupon.created`, `coupon.updated`. Action topics bind a webhook to a WooCommerce action name. Prefer a resource topic unless you need a specific action.
|
|
55
|
+
|
|
56
|
+
Headers on delivery:
|
|
57
|
+
|
|
58
|
+
- `X-WC-Webhook-Source`
|
|
59
|
+
- `X-WC-Webhook-Topic`
|
|
60
|
+
- `X-WC-Webhook-Resource`
|
|
61
|
+
- `X-WC-Webhook-Event`
|
|
62
|
+
- `X-WC-Webhook-Signature`
|
|
63
|
+
- `X-WC-Webhook-ID`
|
|
64
|
+
- `X-WC-Webhook-Delivery-ID`
|
|
65
|
+
|
|
66
|
+
`X-WC-Webhook-Delivery-ID` changes on every attempt. Do not use it as the only idempotency key.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wordpress-plugin-security
|
|
3
|
+
description: "Review and implement WordPress plugin security: capabilities, nonces, sanitization, escaping, prepared SQL, and REST permission callbacks. Use when adding admin screens, AJAX, uploads, hooks, or REST routes in a WordPress or WooCommerce plugin."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Naser Sadrmanesh
|
|
7
|
+
version: "0.3.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# WordPress plugin security
|
|
11
|
+
|
|
12
|
+
Apply this skill to every new entry point: admin form, admin-ajax, admin-post, REST route, Store API extension, file upload, and any hook that reads `$_POST`, `$_GET`, or `$_REQUEST`.
|
|
13
|
+
|
|
14
|
+
WooCommerce data access is covered by the WooCommerce plugin skill. This skill is the authorization and input/output layer around it.
|
|
15
|
+
|
|
16
|
+
## Procedure
|
|
17
|
+
|
|
18
|
+
1. Name the actor and the capability. Check `current_user_can()` with a capability, not a role name. WooCommerce capabilities include `manage_woocommerce`, `edit_shop_orders`, `edit_products`, and `edit_shop_coupons`.
|
|
19
|
+
2. For a state change that uses a cookie session, require a nonce. Nonces stop cross-site request forgery. They do not identify the user and they expire.
|
|
20
|
+
3. Sanitize input into a known type, then pass that value to CRUD or to `$wpdb->prepare()`. Escape at the point of HTML, attribute, or URL output.
|
|
21
|
+
4. Give every REST route a real `permission_callback`. `__return_true` is only acceptable for data you would publish to anonymous visitors.
|
|
22
|
+
5. Fail closed. A missing order, a failed nonce, or a missing capability returns without writing.
|
|
23
|
+
|
|
24
|
+
Use [the review checklist](references/review-checklist.md) before finishing a change.
|
|
25
|
+
|
|
26
|
+
## Forms and admin-post
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
function example_save_desk_note() {
|
|
30
|
+
if ( ! current_user_can( 'edit_shop_orders' ) ) {
|
|
31
|
+
wp_die( esc_html__( 'You cannot edit orders.', 'example-plugin' ) );
|
|
32
|
+
}
|
|
33
|
+
check_admin_referer( 'example_order_panel', 'example_order_nonce' );
|
|
34
|
+
|
|
35
|
+
$order = wc_get_order( absint( wp_unslash( $_POST['order_id'] ?? 0 ) ) );
|
|
36
|
+
if ( ! $order ) {
|
|
37
|
+
wp_die( esc_html__( 'Order not found.', 'example-plugin' ) );
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
$note = sanitize_text_field( wp_unslash( $_POST['desk_note'] ?? '' ) );
|
|
41
|
+
$order->update_meta_data( '_desk_note', $note );
|
|
42
|
+
$order->save();
|
|
43
|
+
}
|
|
44
|
+
add_action( 'admin_post_example_save_desk_note', 'example_save_desk_note' );
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Unslash, then sanitize. `wp_unslash()` reverses magic quotes behavior that WordPress still normalizes for. Cast ids with `absint()`. Allow limited HTML only with `wp_kses()` or `wp_kses_post()` against an explicit allow list.
|
|
48
|
+
|
|
49
|
+
## AJAX
|
|
50
|
+
|
|
51
|
+
- Logged-in actions use `wp_ajax_{action}`.
|
|
52
|
+
- Anonymous actions use `wp_ajax_nopriv_{action}` and must not trust the visitor with shop data.
|
|
53
|
+
- Verify with `check_ajax_referer()`.
|
|
54
|
+
- Return JSON with `wp_send_json_success()` or `wp_send_json_error()`. Do not echo SQL, file paths, or secrets in the error payload.
|
|
55
|
+
|
|
56
|
+
## REST routes
|
|
57
|
+
|
|
58
|
+
```php
|
|
59
|
+
register_rest_route(
|
|
60
|
+
'example/v1',
|
|
61
|
+
'/orders/(?P<id>\\d+)/desk-note',
|
|
62
|
+
array(
|
|
63
|
+
'methods' => WP_REST_Server::EDITABLE,
|
|
64
|
+
'callback' => 'example_rest_save_desk_note',
|
|
65
|
+
'permission_callback' => static function () {
|
|
66
|
+
return current_user_can( 'edit_shop_orders' );
|
|
67
|
+
},
|
|
68
|
+
'args' => array(
|
|
69
|
+
'id' => array( 'sanitize_callback' => 'absint' ),
|
|
70
|
+
'desk_note' => array( 'sanitize_callback' => 'sanitize_text_field' ),
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The permission callback must authorize the object, not only the route. A user who can `edit_shop_orders` may still be blocked by a custom order cap. Check the specific order when the shop uses per-order restrictions.
|
|
77
|
+
|
|
78
|
+
Do not accept a user id, order id, or price from the client as proof of identity. Load the object and derive the price on the server.
|
|
79
|
+
|
|
80
|
+
## SQL
|
|
81
|
+
|
|
82
|
+
```php
|
|
83
|
+
$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );
|
|
84
|
+
$rows = $wpdb->get_results(
|
|
85
|
+
$wpdb->prepare(
|
|
86
|
+
'SELECT id FROM ' . $wpdb->prefix . 'wc_orders WHERE billing_email = %s',
|
|
87
|
+
$email
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Pass every variable through `prepare()`. Do not concatenate table names from request data. Hard-code a known suffix and prefix it with `$wpdb->prefix`. Prefer `wc_get_orders()` for order lookup. The SQL above is only a pattern for a report you have already decided must run against the HPOS orders table.
|
|
93
|
+
|
|
94
|
+
## Uploads and files
|
|
95
|
+
|
|
96
|
+
- Use `wp_handle_upload()` and check the returned error.
|
|
97
|
+
- Allow a short extension list. Do not trust the browser MIME type alone.
|
|
98
|
+
- Store uploads outside executable locations when they are not meant to be public. Never use a request parameter as a filesystem path.
|
|
99
|
+
- `include` and `require` only files whose paths are built from constants you control.
|
|
100
|
+
|
|
101
|
+
## Gotchas
|
|
102
|
+
|
|
103
|
+
- `check_ajax_referer()` dies on failure by default. That is fine for AJAX. For a REST request, return a `WP_Error` with status 403 instead of dying.
|
|
104
|
+
- Nonces in HTML must be created for the same action string you later verify. Copying a WooCommerce nonce from another screen will fail, and it should.
|
|
105
|
+
- `current_user_can( 'administrator' )` misses shop managers who legitimately have `manage_woocommerce`, and it matches the wrong concept. Check the capability.
|
|
106
|
+
- `esc_html()` does not make a URL safe. Use `esc_url()`. `esc_attr()` does not make HTML body text safe. Escape for the context you are in.
|
|
107
|
+
- `unserialize()` on stored or posted data can instantiate objects. Store JSON, or use `maybe_unserialize()` only on data your own code wrote, and prefer JSON for new meta.
|
|
108
|
+
- A hidden form field such as `order_id` is attacker-controlled. Capability checks happen after you load that order, not instead of loading it.
|
|
109
|
+
- Logging `$_POST` on a checkout request can capture payment fields. Log ids and your own error codes.
|
|
110
|
+
|
|
111
|
+
## Verification
|
|
112
|
+
|
|
113
|
+
- Repeat the write while logged out. It does not change data.
|
|
114
|
+
- Repeat the write as a subscriber or customer. It does not change shop data.
|
|
115
|
+
- Repeat the write with the nonce removed. It does not change data.
|
|
116
|
+
- A REST callback with the permission callback removed is a failed review, even if the happy path works.
|
|
117
|
+
- An error response does not include a secret, a stack trace, or another customer's data.
|
|
118
|
+
|
|
119
|
+
## Evaluation
|
|
120
|
+
|
|
121
|
+
Worked examples and the checks they must satisfy are in [evals/tasks.json](evals/tasks.json).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Verify the user and the nonce, then sanitize the field.
|
|
2
|
+
|
|
3
|
+
```php
|
|
4
|
+
if ( ! current_user_can( 'edit_shop_orders' ) ) {
|
|
5
|
+
wp_die( esc_html__( 'You cannot edit orders.', 'example-plugin' ) );
|
|
6
|
+
}
|
|
7
|
+
check_admin_referer( 'example_order_panel' );
|
|
8
|
+
$note = sanitize_text_field( wp_unslash( $_POST['desk_note'] ?? '' ) );
|
|
9
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tasks": [
|
|
3
|
+
{
|
|
4
|
+
"id": "private-rest-route",
|
|
5
|
+
"task": "Register a REST route that updates an order note and rejects shoppers who cannot edit orders.",
|
|
6
|
+
"expects": {
|
|
7
|
+
"must_include": [
|
|
8
|
+
"permission_callback",
|
|
9
|
+
"current_user_can",
|
|
10
|
+
"edit_shop_orders"
|
|
11
|
+
],
|
|
12
|
+
"must_not_include": [
|
|
13
|
+
"__return_true"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "admin-form-nonce",
|
|
19
|
+
"task": "Save an admin form that changes an order only when the user has the capability and a valid nonce.",
|
|
20
|
+
"expects": {
|
|
21
|
+
"must_include": [
|
|
22
|
+
"check_admin_referer",
|
|
23
|
+
"current_user_can"
|
|
24
|
+
],
|
|
25
|
+
"must_not_include": [
|
|
26
|
+
"unserialize("
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Plugin security review
|
|
2
|
+
|
|
3
|
+
Check each new entry point. Skip a line only when it does not apply, and say why.
|
|
4
|
+
|
|
5
|
+
## Entry
|
|
6
|
+
|
|
7
|
+
- The file starts with `defined( 'ABSPATH' ) || exit;` or is loaded only from a file that does.
|
|
8
|
+
- Admin-post, admin-ajax, REST, and form handlers each have their own capability check.
|
|
9
|
+
- State-changing browser requests verify a nonce bound to that action.
|
|
10
|
+
- The REST `permission_callback` is a named function or closure that returns a capability check, never `__return_true`, for private data.
|
|
11
|
+
|
|
12
|
+
## Input
|
|
13
|
+
|
|
14
|
+
- Request values go through `wp_unslash()` and then a sanitizer: `absint`, `sanitize_text_field`, `sanitize_email`, `esc_url_raw`, `wp_kses_post`, or a strict allow list.
|
|
15
|
+
- Prices, quantities, and statuses are validated against server-side rules. The client amount is not stored as the charged amount.
|
|
16
|
+
- Sort columns, table names, and file paths are chosen from a fixed map, not from the request string.
|
|
17
|
+
- Uploads use `wp_handle_upload()` and an extension allow list.
|
|
18
|
+
|
|
19
|
+
## Output and storage
|
|
20
|
+
|
|
21
|
+
- HTML text uses `esc_html()`. Attributes use `esc_attr()`. URLs use `esc_url()`. Translated strings use the escaping gettext variant.
|
|
22
|
+
- SQL variables use `$wpdb->prepare()`.
|
|
23
|
+
- New meta and options are JSON or scalars. No `unserialize()` of request data.
|
|
24
|
+
- Secrets and card data are not written to logs, order notes, or the browser.
|
|
25
|
+
|
|
26
|
+
## WooCommerce-specific
|
|
27
|
+
|
|
28
|
+
- Order and product writes use CRUD.
|
|
29
|
+
- The handler works for HPOS when it touches orders.
|
|
30
|
+
- Checkout logic that must run for blocks is hooked to the Store API, not only to classic checkout.
|
|
31
|
+
- Webhook handlers verify a signature instead of using a nonce. Nonces are for cookie sessions. Webhooks are not cookie sessions.
|