@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,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tasks": [
|
|
3
|
+
{
|
|
4
|
+
"id": "one-renewal",
|
|
5
|
+
"task": "Create a single renewal charge for a subscription period and retry it without opening a second period.",
|
|
6
|
+
"expects": {
|
|
7
|
+
"must_include": [
|
|
8
|
+
"idempotency",
|
|
9
|
+
"current_period_end"
|
|
10
|
+
],
|
|
11
|
+
"must_not_include": [
|
|
12
|
+
"wp_insert_post"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "cancel-at-period-end",
|
|
18
|
+
"task": "Let a shopper cancel at period end without removing access before the period they already paid for.",
|
|
19
|
+
"expects": {
|
|
20
|
+
"must_include": [
|
|
21
|
+
"cancel_at_period_end",
|
|
22
|
+
"active"
|
|
23
|
+
],
|
|
24
|
+
"must_not_include": [
|
|
25
|
+
"status = canceled immediately"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Subscription states
|
|
2
|
+
|
|
3
|
+
## Allowed transitions
|
|
4
|
+
|
|
5
|
+
| From | Event | To |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `trialing` | Trial converts and the first charge succeeds | `active` |
|
|
8
|
+
| `trialing` | Shopper cancels during the trial | `canceled` |
|
|
9
|
+
| `active` | Renewal charge succeeds | `active` with the next period |
|
|
10
|
+
| `active` | Renewal charge fails | `past_due` |
|
|
11
|
+
| `active` | Cancel at period end | `active` with `cancel_at_period_end` |
|
|
12
|
+
| `active` plus `cancel_at_period_end` | Period end | `canceled` |
|
|
13
|
+
| `past_due` | Retry or updated payment method succeeds | `active` |
|
|
14
|
+
| `past_due` | Retry schedule exhausted | `canceled` or access-off, as configured |
|
|
15
|
+
| `active` or `past_due` | Pause | `paused` |
|
|
16
|
+
| `paused` | Resume | `active` only after a successful charge if a period was missed |
|
|
17
|
+
|
|
18
|
+
No other transition should be reachable from a client request. Unknown provider statuses do not move the contract. Store the raw provider status beside your mapped status so a person can reconcile it.
|
|
19
|
+
|
|
20
|
+
## Platform mapping
|
|
21
|
+
|
|
22
|
+
- Shopify: selling plans and subscription contracts are the native model. Follow the subscription APIs for the app's configured version. Do not store the contract only as a metafield you update from the browser.
|
|
23
|
+
- WooCommerce Subscriptions: it is not WooCommerce core. Renewal orders and payment completion belong to that extension. Do not create renewal orders with `wp_insert_post` or `update_post_meta`. If you are on HPOS, use the extension's order APIs the same way the WooCommerce plugin skill requires for any other order.
|
|
24
|
+
|
|
25
|
+
## Idempotency key
|
|
26
|
+
|
|
27
|
+
`sub_{subscriptionId}_period_{periodStartIso}`. Reuse it for every retry of that period. A plan change that creates a proration invoice uses a different key that includes the change id.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tax-and-shipping
|
|
3
|
+
description: "Calculate tax and shipping from the destination and the store's configured rules, and store the breakdown on the order. Use when changing tax classes, shipping zones, rates, or the amount shown before payment."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Naser Sadrmanesh
|
|
7
|
+
version: "0.3.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Tax and shipping
|
|
11
|
+
|
|
12
|
+
Use this skill when an order needs a tax or shipping amount. Checkout display is the checkout skill. Payment capture uses the total this skill produced. Do not invent a tax rate. A rate comes from the store's configuration or from a tax service the store has enabled.
|
|
13
|
+
|
|
14
|
+
## Procedure
|
|
15
|
+
|
|
16
|
+
1. Decide the tax address from the store's tax setting: destination, shop base, or the service's rule. Do not assume every country taxes the shipping address.
|
|
17
|
+
2. Resolve each line to a tax class. Shipping may be taxed separately from goods.
|
|
18
|
+
3. Calculate tax in integer minor units. Store each tax line: name or rate id, rate, and amount. Record whether prices are entered inclusive of tax.
|
|
19
|
+
4. Resolve shipping from the destination to a zone, then to the methods the shopper can choose. Quote those rates before payment.
|
|
20
|
+
5. Recalculate when the address, the line items, or the selected method changes. The total you charge is the last server calculation, not the first estimate.
|
|
21
|
+
|
|
22
|
+
WooCommerce mapping is in [the WooCommerce notes](references/woocommerce.md). Worked examples and checks are in [evals/tasks.json](evals/tasks.json).
|
|
23
|
+
|
|
24
|
+
## Tax
|
|
25
|
+
|
|
26
|
+
- A missing rate is an error or a zero-rated class the merchant configured. It is not a silent 0% you made up.
|
|
27
|
+
- Inclusive and exclusive prices do not mix on one order. If the catalog is inclusive, the line price already contains tax and the tax line is the included portion.
|
|
28
|
+
- Discounts reduce the taxable base according to the store's setting (before tax or after). Apply that setting once.
|
|
29
|
+
- Digital and physical lines can have different classes. Do not put one rate on the order header and ignore the lines.
|
|
30
|
+
- Keep the rate id that was used. A later change to the rate table must not rewrite historical orders.
|
|
31
|
+
|
|
32
|
+
## Shipping
|
|
33
|
+
|
|
34
|
+
- Zones are matched from the most specific address you have. Quoting a domestic rate before the country is known is an estimate and must be replaced.
|
|
35
|
+
- A free method is a configured rate of zero. An address with no zone is not free shipping. Show that you cannot ship there.
|
|
36
|
+
- The selected method's id and the quoted amount are stored on the order. Changing the label later does not change the amount already charged.
|
|
37
|
+
- Multi-vendor shipments use one rate per merchant policy. The payout skill records who receives the shipping amount. This skill only calculates what the shopper pays.
|
|
38
|
+
|
|
39
|
+
## Presentation
|
|
40
|
+
|
|
41
|
+
- Show shipping, tax, and the grand total before the pay control.
|
|
42
|
+
- A second currency is a display conversion. The tax and shipping amounts you persist use the order currency.
|
|
43
|
+
- Round each tax line with the currency's minor units, then sum. Do not sum binary floats and round once at the end if the platform's tax API already defines the rounding.
|
|
44
|
+
|
|
45
|
+
## Gotchas
|
|
46
|
+
|
|
47
|
+
- Calculating tax only in the browser lets a modified client submit a lower total. Recalculate on the server.
|
|
48
|
+
- Caching a shipping quote without the address, the items, and the method id will reuse another shopper's rate.
|
|
49
|
+
- Tax-exempt customers need an explicit exemption on the customer or the order. Skipping tax because a field was blank marks everyone exempt.
|
|
50
|
+
- Refunds refund tax in proportion to the refunded lines, using the tax that was stored, not today's rate table.
|
|
51
|
+
|
|
52
|
+
## Verification
|
|
53
|
+
|
|
54
|
+
- Two addresses in different zones produce the rates configured for those zones, including a zone that does not ship.
|
|
55
|
+
- An order stores tax lines that add up to the tax total, in the order currency.
|
|
56
|
+
- Changing the address on checkout updates the quoted shipping and tax before payment.
|
|
57
|
+
- A historical order keeps its original tax amounts after you edit the live rate table.
|
|
58
|
+
|
|
59
|
+
## Evaluation
|
|
60
|
+
|
|
61
|
+
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": "store-tax-lines",
|
|
5
|
+
"task": "Persist the tax calculated for an order so a later rate-table edit does not change that order.",
|
|
6
|
+
"expects": {
|
|
7
|
+
"must_include": [
|
|
8
|
+
"rate id",
|
|
9
|
+
"minor"
|
|
10
|
+
],
|
|
11
|
+
"must_not_include": [
|
|
12
|
+
"hard-coded 20%"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "unknown-zone",
|
|
18
|
+
"task": "Quote shipping only from the destination zone and refuse to invent a free rate when no zone matches.",
|
|
19
|
+
"expects": {
|
|
20
|
+
"must_include": [
|
|
21
|
+
"destination",
|
|
22
|
+
"no zone"
|
|
23
|
+
],
|
|
24
|
+
"must_not_include": [
|
|
25
|
+
"cost of 0"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# WooCommerce tax and shipping
|
|
2
|
+
|
|
3
|
+
Use WooCommerce's tax and shipping services. Do not reimplement a rate table in the theme.
|
|
4
|
+
|
|
5
|
+
## Tax
|
|
6
|
+
|
|
7
|
+
- Prices inclusive or exclusive are a store setting (`woocommerce_prices_include_tax`). Read it. Do not hard-code one mode in a plugin.
|
|
8
|
+
- Tax classes live on the product. Shipping tax class is its own setting.
|
|
9
|
+
- `WC_Tax::calc_tax()` and `WC_Tax::calc_shipping_tax()` take the store's rates. Pass amounts the way those methods document for the installed version, and keep minor-unit rounding consistent with `wc_get_price_decimals()`.
|
|
10
|
+
- Order item tax is stored on the line (`total_tax`, `subtotal_tax`, and the rate id maps). Read it back from the order item. Do not recompute history from the current rates.
|
|
11
|
+
- Checkout recalculates totals through `WC_Cart::calculate_totals()`. A custom fee or price change belongs in `woocommerce_before_calculate_totals` or a fee hook so tax runs after it.
|
|
12
|
+
|
|
13
|
+
## Shipping
|
|
14
|
+
|
|
15
|
+
- Zones, locations, and method instances are configured in WooCommerce. A method extends `WC_Shipping_Method` and implements `calculate_shipping( $package )`.
|
|
16
|
+
- `calculate_shipping` adds rates with `add_rate()`. The `cost` is a decimal string in the store currency. A cost of `0` is a free rate you intend to offer.
|
|
17
|
+
- The package destination is in `$package['destination']`. If the country is empty, do not add a domestic rate as if it were final.
|
|
18
|
+
- The chosen rate is stored on the shipping order item (`method_id`, `instance_id`, `total`). HPOS does not change that. Use the order item API.
|
|
19
|
+
|
|
20
|
+
## Display
|
|
21
|
+
|
|
22
|
+
`wc_price()` formats amounts. Tax labels come from the rate's label. Do not concatenate a hard-coded percent next to a price that WooCommerce has already formatted with or without tax.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vector-product-search
|
|
3
|
+
description: "Add vector retrieval beside lexical product search, with one embedding per purchasable unit and exact SKU lookup kept first. Use when adding embedding search, hybrid ranking, or re-embedding after catalog edits."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Naser Sadrmanesh
|
|
7
|
+
version: "0.3.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Vector product search
|
|
11
|
+
|
|
12
|
+
Use this skill when lexical search is not enough for paraphrased queries. Facets, stock, and lexical ranking stay in the marketplace search skill. This skill adds a vector index. It does not replace SKU and GTIN lookup.
|
|
13
|
+
|
|
14
|
+
Do not claim a recall or conversion number. Choose a model, record its id and dimension, and measure queries you have labeled.
|
|
15
|
+
|
|
16
|
+
## Procedure
|
|
17
|
+
|
|
18
|
+
1. Embed one text per purchasable unit. A variant with its own title or attributes gets its own vector. Copy the parent name into that text.
|
|
19
|
+
2. Use the same model, dimension, and similarity metric for indexing and querying. Record `model_id` and a hash of the embedded text on the document.
|
|
20
|
+
3. Keep price, stock, and facet ids as filter fields. Do not rely on the embedding to enforce "blue" or "in stock".
|
|
21
|
+
4. Resolve an exact normalized SKU or GTIN before any vector query. If it hits one unit, return that unit first.
|
|
22
|
+
5. Combine lexical and vector scores with a formula you write down. Re-embed when the text hash changes, not when only the price changes.
|
|
23
|
+
|
|
24
|
+
Normalization and hybrid scoring are in [the index notes](references/index.md). Worked examples and checks are in [evals/tasks.json](evals/tasks.json).
|
|
25
|
+
|
|
26
|
+
## What is embedded
|
|
27
|
+
|
|
28
|
+
Include the fields a shopper might paraphrase: title, brand, category names, and attribute labels. Run the same text normalization as lexical search, including Persian Yeh, Kaf, digits, and ZWNJ removal, before you embed.
|
|
29
|
+
|
|
30
|
+
Exclude price, stock counts, and SKUs from the embedded text. Price and stock change without the words changing, and a SKU is handled by exact match. If you later include them, the content hash must change when they change.
|
|
31
|
+
|
|
32
|
+
## Query
|
|
33
|
+
|
|
34
|
+
1. Normalize the query.
|
|
35
|
+
2. Exact identifier match wins.
|
|
36
|
+
3. Otherwise run the lexical query and the vector query with the same filters (facet ids, stock policy, category).
|
|
37
|
+
4. Fuse the two ranked lists. A default you may start from is a weighted sum after each list is normalized to the same scale. Change the weight only when labeled queries say so.
|
|
38
|
+
5. Page with a keyset on the fused score plus `unit_id`.
|
|
39
|
+
|
|
40
|
+
Apply filters in both retrievals. A vector hit that ignores the selected color is a bug.
|
|
41
|
+
|
|
42
|
+
## Re-embedding
|
|
43
|
+
|
|
44
|
+
- Store `model_id`, dimension, metric (`cosine` unless the index documents another), and `text_hash`.
|
|
45
|
+
- A catalog write recomputes the hash. If the hash or the model id differs, enqueue that unit.
|
|
46
|
+
- Switching models rebuilds the index. Do not query an index with a different model than the one that wrote it.
|
|
47
|
+
- Delete the vector when the unit is deleted. A parent rebuild replaces that parent's vectors as a set.
|
|
48
|
+
|
|
49
|
+
## Gotchas
|
|
50
|
+
|
|
51
|
+
- Embedding only the parent returns one vector for every size and color.
|
|
52
|
+
- Cosine and dot product are not interchangeable unless the vectors are normalized and you meant that equivalence.
|
|
53
|
+
- A vector score must not outrank an exact SKU match.
|
|
54
|
+
- Indexing the raw Persian string and querying the normalized string misses. Normalize both.
|
|
55
|
+
- Do not log shopper queries next to identifiers you are not allowed to retain. The query text can contain an address or a name the shopper pasted.
|
|
56
|
+
|
|
57
|
+
## Verification
|
|
58
|
+
|
|
59
|
+
- A SKU query returns that unit even when a nearer vector belongs to another product.
|
|
60
|
+
- A color filter removes other colors from the vector results, not only from the lexical results.
|
|
61
|
+
- Changing the price does not change `text_hash`. Changing the title does, and that unit is re-embedded.
|
|
62
|
+
- Deleting a variant removes its vector.
|
|
63
|
+
|
|
64
|
+
## Evaluation
|
|
65
|
+
|
|
66
|
+
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": "sku-before-vector",
|
|
5
|
+
"task": "Return the product whose SKU matches the query before any nearest-vector result.",
|
|
6
|
+
"expects": {
|
|
7
|
+
"must_include": [
|
|
8
|
+
"sku",
|
|
9
|
+
"vector"
|
|
10
|
+
],
|
|
11
|
+
"must_not_include": [
|
|
12
|
+
"embedding of the price"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "reembed-on-title",
|
|
18
|
+
"task": "Re-embed a variant when its title changes and skip re-embedding when only the price changes.",
|
|
19
|
+
"expects": {
|
|
20
|
+
"must_include": [
|
|
21
|
+
"text_hash",
|
|
22
|
+
"model_id"
|
|
23
|
+
],
|
|
24
|
+
"must_not_include": [
|
|
25
|
+
"cosine and dot product"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Vector index beside lexical search
|
|
2
|
+
|
|
3
|
+
## Document
|
|
4
|
+
|
|
5
|
+
| Field | Role |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `unit_id` | Purchasable unit. Primary key. |
|
|
8
|
+
| `parent_id` | Set for variants. |
|
|
9
|
+
| `sku`, `gtin` | Exact match. Not part of the embedded text. |
|
|
10
|
+
| `text_hash` | Hash of the normalized text that was embedded. |
|
|
11
|
+
| `model_id` | Embedding model identifier and its dimension. |
|
|
12
|
+
| `embedding` | Vector from that model only. |
|
|
13
|
+
| `facet_ids`, `in_stock`, `price`, `currency` | Filters and display. Not a reason to re-embed. |
|
|
14
|
+
|
|
15
|
+
## Embedded text
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
{title}
|
|
19
|
+
{brand}
|
|
20
|
+
{category names}
|
|
21
|
+
{attribute labels}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Normalize with the marketplace search normalizer before hashing and embedding. The hash is of the normalized text. If you change the normalizer, rebuild the index. Old hashes would not match the new text.
|
|
25
|
+
|
|
26
|
+
## Fusion
|
|
27
|
+
|
|
28
|
+
Normalize each retrieval's scores onto `[0, 1]` within that result set, then:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
fused = (lexical_weight * lexical) + (vector_weight * vector)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Start at `lexical_weight = 0.6` and `vector_weight = 0.4` if you have no labeled queries yet. This is a starting point, not a measured optimum. Units that appear in only one list use `0` for the missing score.
|
|
35
|
+
|
|
36
|
+
Exact SKU or GTIN matches skip fusion and are placed first.
|
|
37
|
+
|
|
38
|
+
## Metric
|
|
39
|
+
|
|
40
|
+
Write down cosine similarity or the index's metric next to `model_id`. Query with that metric. Do not mix cosine in one environment and inner product in another unless both sides use normalized vectors and you have recorded that they match.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: woocommerce-plugin-development
|
|
3
|
+
description: "Build and change WooCommerce plugins with hooks, CRUD data stores, HPOS-safe order access, nonces, capabilities, and gettext. Use when writing or reviewing a plugin that reads or writes WooCommerce products, orders, customers, or checkout."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Naser Sadrmanesh
|
|
7
|
+
version: "0.3.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# WooCommerce plugin development
|
|
11
|
+
|
|
12
|
+
Use this skill for a WordPress plugin that depends on WooCommerce. Use the REST API skill for an external HTTP client. Use the payment and webhook skills for capture and notification handling. Use the WordPress plugin security skill for the authorization details of any new endpoint.
|
|
13
|
+
|
|
14
|
+
## Procedure
|
|
15
|
+
|
|
16
|
+
1. Declare the dependency in the main plugin file. WordPress 6.5+ understands `Requires Plugins: woocommerce`. Set `WC tested up to` as well. WooCommerce only surfaces High-Performance Order Storage (HPOS) compatibility in the plugins list when that header is present.
|
|
17
|
+
2. Stop direct requests with `defined( 'ABSPATH' ) || exit;`.
|
|
18
|
+
3. Read and write products and orders through CRUD. Load with `wc_get_product()` or `wc_get_order()`, change setters, then call `save()` once. List orders with `wc_get_orders()`. Do not query `wp_posts` for `shop_order`.
|
|
19
|
+
4. From the main plugin file, on `before_woocommerce_init`, declare feature compatibility. Pass `__FILE__` from that main file. Declare `custom_order_tables` only when every order read and write uses CRUD or `OrderUtil`. Declare `cart_checkout_blocks` only when checkout behavior also runs for the Checkout block.
|
|
20
|
+
5. Hook the specific action. Cover both checkout stacks when the code runs during checkout. Open [the hook notes](references/hooks.md) before choosing one.
|
|
21
|
+
6. Authorize every write with a capability and a nonce. A nonce is not authentication.
|
|
22
|
+
7. Send user-facing strings through gettext with the plugin text domain.
|
|
23
|
+
|
|
24
|
+
A skeleton header and the compatibility declaration are in [`assets/main-plugin-file.php`](assets/main-plugin-file.php). Copy it into a new plugin only after you delete the declaration you cannot yet keep.
|
|
25
|
+
|
|
26
|
+
## Order and product writes
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
$order = wc_get_order( $order_id );
|
|
30
|
+
if ( ! $order ) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
$order->update_meta_data( '_carrier_code', sanitize_text_field( $carrier ) );
|
|
34
|
+
$order->save();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Do not call `update_post_meta()`, `get_post_meta()`, `wp_insert_post()`, or `get_posts()` for orders. Those use the posts tables. Under HPOS the order may be absent there, or the row may be a stale compatibility copy.
|
|
38
|
+
|
|
39
|
+
HPOS is the default for new installations from WooCommerce 8.2 onward. Older shops may still use posts storage. CRUD serves both. Compatibility mode can mirror orders into posts; code that reads those posts still fails when a merchant turns the mirror off.
|
|
40
|
+
|
|
41
|
+
Open [the HPOS notes](references/hpos.md) before adding an admin order screen, a custom order query, or SQL.
|
|
42
|
+
|
|
43
|
+
## Checkout hooks
|
|
44
|
+
|
|
45
|
+
Classic shortcode checkout and the Checkout block do not share one hook.
|
|
46
|
+
|
|
47
|
+
- Classic checkout, before the first save: `woocommerce_checkout_create_order` receives the `WC_Order` and the posted data.
|
|
48
|
+
- Classic checkout, after the order exists: `woocommerce_checkout_order_processed`.
|
|
49
|
+
- Checkout block and Store API: `woocommerce_store_api_checkout_update_order_from_request` and `woocommerce_store_api_checkout_order_processed`.
|
|
50
|
+
|
|
51
|
+
A plugin that hooks only the classic actions does not run during block checkout.
|
|
52
|
+
|
|
53
|
+
## Admin order screens
|
|
54
|
+
|
|
55
|
+
`wc_get_page_screen_id( 'shop-order' )` returns `shop_order` on posts storage and the HPOS orders screen id otherwise. Use it for metaboxes. The callback may receive a `WP_Post` or a `WC_Order`. Normalize with `wc_get_order()` and ignore the original argument after that. Link to an order with `$order->get_edit_order_url()`.
|
|
56
|
+
|
|
57
|
+
`save_post_shop_order` does not run for HPOS orders. Persist admin fields from `woocommerce_process_shop_order_meta`, then `wc_get_order()` and `save()`.
|
|
58
|
+
|
|
59
|
+
## Internationalization
|
|
60
|
+
|
|
61
|
+
- The text domain matches the plugin slug. Set the `Text Domain` header.
|
|
62
|
+
- Print with `esc_html__()`, `esc_attr__()`, and `esc_html_e()`. Use `sprintf` placeholders. Do not concatenate translated fragments.
|
|
63
|
+
- Language packs for plugins hosted on WordPress.org load from the slug. For any other distribution, call `load_plugin_textdomain()` on `init`. WordPress 6.7 reports a notice when translations load earlier.
|
|
64
|
+
- RTL layout is a separate task. Use the Persian and RTL skill when the storefront needs it.
|
|
65
|
+
|
|
66
|
+
## Gotchas
|
|
67
|
+
|
|
68
|
+
- `save()` writes the object. Set the fields for this change, then save once.
|
|
69
|
+
- Refunds are orders of type `shop_order_refund`. Create them with `wc_create_refund()`. Flipping the parent status to `refunded` does not record a refund.
|
|
70
|
+
- Stock reduction is tied to payment completion and status transitions. A second decrement double-counts. Core tracks this with `_order_stock_reduced` inside `wc_maybe_reduce_stock_levels()`.
|
|
71
|
+
- `woocommerce_checkout_update_order_meta` still runs. Examples that call `update_post_meta()` inside it are not HPOS-safe.
|
|
72
|
+
- Declaring `custom_order_tables` compatibility `true` is a promise. If any order path still uses post APIs, declare `false` until that path is gone.
|
|
73
|
+
- SQL against `{$wpdb->prefix}wc_orders` skips caches and the posts fallback. Limit it to a measured report, and branch with `OrderUtil::custom_orders_table_usage_is_enabled()`.
|
|
74
|
+
|
|
75
|
+
## Verification
|
|
76
|
+
|
|
77
|
+
- With HPOS on and compatibility mode off, create and edit an order through the plugin. The change is visible in admin and in `wc_get_order()`.
|
|
78
|
+
- If both checkouts are enabled, place one classic order and one block order.
|
|
79
|
+
- A user without the required capability cannot perform the write. A missing or reused nonce is rejected.
|
|
80
|
+
- New strings are escaped where printed and are picked up by `wp i18n make-pot`.
|
|
81
|
+
|
|
82
|
+
## Evaluation
|
|
83
|
+
|
|
84
|
+
Worked examples and the checks they must satisfy are in [evals/tasks.json](evals/tasks.json).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Name: Example Commerce Plugin
|
|
4
|
+
* Description: Skeleton. Replace the name, slug, and compatibility flags before shipping.
|
|
5
|
+
* Version: 0.1.0
|
|
6
|
+
* Author: Replace with the plugin author
|
|
7
|
+
* Text Domain: example-plugin
|
|
8
|
+
* Requires Plugins: woocommerce
|
|
9
|
+
* Replace x.y.z below with the WooCommerce version you actually tested.
|
|
10
|
+
* WC tested up to: x.y.z
|
|
11
|
+
*
|
|
12
|
+
* @package ExamplePlugin
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
defined( 'ABSPATH' ) || exit;
|
|
16
|
+
|
|
17
|
+
add_action(
|
|
18
|
+
'before_woocommerce_init',
|
|
19
|
+
static function () {
|
|
20
|
+
if ( ! class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Keep true only when order access stays on CRUD. Otherwise pass false.
|
|
24
|
+
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
|
|
25
|
+
// Keep true only when checkout changes also run for the Checkout block.
|
|
26
|
+
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true );
|
|
27
|
+
}
|
|
28
|
+
);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Declare compatibility on the WooCommerce init hook from the main plugin file.
|
|
2
|
+
|
|
3
|
+
```php
|
|
4
|
+
add_action( 'before_woocommerce_init', function () {
|
|
5
|
+
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
|
|
6
|
+
} );
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Include a WC tested up to header set to the version you actually tested.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tasks": [
|
|
3
|
+
{
|
|
4
|
+
"id": "save-order-meta",
|
|
5
|
+
"task": "Store a carrier code from the WooCommerce order admin screen using CRUD so the value survives HPOS.",
|
|
6
|
+
"expects": {
|
|
7
|
+
"must_include": [
|
|
8
|
+
"wc_get_order",
|
|
9
|
+
"update_meta_data",
|
|
10
|
+
"->save("
|
|
11
|
+
],
|
|
12
|
+
"must_not_include": [
|
|
13
|
+
"update_post_meta"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "declare-hpos",
|
|
19
|
+
"task": "Declare High-Performance Order Storage compatibility from the main plugin file after order access uses CRUD.",
|
|
20
|
+
"expects": {
|
|
21
|
+
"must_include": [
|
|
22
|
+
"before_woocommerce_init",
|
|
23
|
+
"declare_compatibility",
|
|
24
|
+
"custom_order_tables"
|
|
25
|
+
],
|
|
26
|
+
"must_not_include": [
|
|
27
|
+
"save_post_shop_order"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# WooCommerce hooks
|
|
2
|
+
|
|
3
|
+
Confirm argument order in the WooCommerce version you are running when a signature is load-bearing. Prefer a hook that receives a `WC_Order` or `WC_Product`. If a hook passes only an id, load the object with `wc_get_order()` or `wc_get_product()` and do not touch post meta.
|
|
4
|
+
|
|
5
|
+
## Orders
|
|
6
|
+
|
|
7
|
+
| Hook | Use it for |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `woocommerce_checkout_create_order` | Set order fields and meta during classic checkout, before the first save. |
|
|
10
|
+
| `woocommerce_checkout_order_processed` | React after a classic checkout order exists. |
|
|
11
|
+
| `woocommerce_store_api_checkout_update_order_from_request` | Same moment as classic create, for the Checkout block. |
|
|
12
|
+
| `woocommerce_store_api_checkout_order_processed` | React after a block checkout order exists. |
|
|
13
|
+
| `woocommerce_new_order` | Notice that an order id exists. Status may still be `pending` or `checkout-draft`. |
|
|
14
|
+
| `woocommerce_order_status_changed` | Compare previous and next status. Arguments are order id, old status, new status, and the order. |
|
|
15
|
+
| `woocommerce_payment_complete` | Run side effects after a verified payment. Do not use this as the only stock decrement. |
|
|
16
|
+
| `woocommerce_order_refunded` | React after `wc_create_refund()` has created the refund order. |
|
|
17
|
+
| `woocommerce_before_order_object_save` | Inspect or adjust an order about to be written through CRUD. |
|
|
18
|
+
| `woocommerce_process_shop_order_meta` | Save admin order fields for both storage modes. |
|
|
19
|
+
|
|
20
|
+
Do not use `save_post_shop_order` or `manage_edit-shop_order_columns` as the only integration point. The HPOS list table uses `woocommerce_shop_order_list_table_columns` and `woocommerce_shop_order_list_table_custom_column`. Register the custom-column action with two accepted arguments. The second is a `WC_Order`, not a post id.
|
|
21
|
+
|
|
22
|
+
## Products
|
|
23
|
+
|
|
24
|
+
| Hook | Use it for |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `woocommerce_before_product_object_save` / `woocommerce_after_product_object_save` | Changes made through `WC_Product::save()`. |
|
|
27
|
+
| `woocommerce_new_product` / `woocommerce_update_product` | After a product id exists. |
|
|
28
|
+
| `woocommerce_product_options_general_product_data` | Extra fields on the product data panel. |
|
|
29
|
+
| `woocommerce_process_product_meta` | Save those fields. Check a nonce and `edit_products`. |
|
|
30
|
+
| `woocommerce_product_set_stock` / `woocommerce_variation_set_stock` | React to a stock write. Do not write stock again from the same hook. |
|
|
31
|
+
|
|
32
|
+
## Cart and checkout fields
|
|
33
|
+
|
|
34
|
+
| Hook | Use it for |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `woocommerce_add_to_cart_validation` | Reject a line before it enters the cart. Return false and add a notice. |
|
|
37
|
+
| `woocommerce_before_calculate_totals` | Change line prices for this request. Guard with a flag so the hook does not recurse. |
|
|
38
|
+
| `woocommerce_cart_calculate_fees` | Add a fee. Fees are not a substitute for a payment surcharge unless the store shows the fee before the pay button. |
|
|
39
|
+
| `woocommerce_checkout_fields` | Add, remove, or relabel classic checkout fields. |
|
|
40
|
+
| `woocommerce_default_address_fields` | Change address fields for every country, then override per country with `woocommerce_get_country_locale`. |
|
|
41
|
+
|
|
42
|
+
## Registration
|
|
43
|
+
|
|
44
|
+
Register gateways on `woocommerce_payment_gateways`. Register settings on `woocommerce_get_settings_{tab}`.
|
|
45
|
+
|
|
46
|
+
Run WooCommerce callbacks after WooCommerce has loaded. `plugins_loaded` at the default priority is too early if your plugin file loads before WooCommerce. Prefer `woocommerce_loaded`, or a later `plugins_loaded` priority, before calling `wc_get_order()` during bootstrap.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# HPOS order access
|
|
2
|
+
|
|
3
|
+
High-Performance Order Storage keeps orders in dedicated tables:
|
|
4
|
+
|
|
5
|
+
- `{prefix}wc_orders`
|
|
6
|
+
- `{prefix}wc_order_addresses`
|
|
7
|
+
- `{prefix}wc_order_operational_data`
|
|
8
|
+
- `{prefix}wc_orders_meta`
|
|
9
|
+
|
|
10
|
+
Posts storage keeps the same objects in `wp_posts` and `wp_postmeta` as the `shop_order` post type. Refunds use `shop_order_refund` in both modes. CRUD hides that choice. Direct SQL and post APIs do not.
|
|
11
|
+
|
|
12
|
+
## Detect the active store
|
|
13
|
+
|
|
14
|
+
```php
|
|
15
|
+
use Automattic\WooCommerce\Utilities\OrderUtil;
|
|
16
|
+
|
|
17
|
+
$hpos = class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled();
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use this to branch SQL or admin screen registration. Do not use it to decide whether `wc_get_order()` works. That function works in both modes.
|
|
21
|
+
|
|
22
|
+
## Declare compatibility
|
|
23
|
+
|
|
24
|
+
Call this from the main plugin file. `declare_compatibility()` returns false outside `before_woocommerce_init`.
|
|
25
|
+
|
|
26
|
+
```php
|
|
27
|
+
add_action( 'before_woocommerce_init', function () {
|
|
28
|
+
if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
|
|
29
|
+
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
|
|
30
|
+
}
|
|
31
|
+
} );
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If the declaration lives in another file, pass the main plugin file path, such as `example-plugin/example-plugin.php`, instead of `__FILE__`.
|
|
35
|
+
|
|
36
|
+
## Queries
|
|
37
|
+
|
|
38
|
+
```php
|
|
39
|
+
$results = wc_get_orders(
|
|
40
|
+
array(
|
|
41
|
+
'status' => array( 'processing', 'completed' ),
|
|
42
|
+
'limit' => 50,
|
|
43
|
+
'paginate' => true,
|
|
44
|
+
'return' => 'objects',
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Filter custom meta with `meta_query` only when the cardinality is small enough to measure. There is no unique index on arbitrary meta keys. An idempotency lookup by meta is not a uniqueness constraint.
|
|
50
|
+
|
|
51
|
+
Identify an order with `OrderUtil::is_order()` and `OrderUtil::get_order_type()`. Do not call `get_post_type()` for this.
|
|
52
|
+
|
|
53
|
+
## Admin metabox
|
|
54
|
+
|
|
55
|
+
```php
|
|
56
|
+
add_action( 'add_meta_boxes', function () {
|
|
57
|
+
if ( ! function_exists( 'wc_get_page_screen_id' ) ) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
add_meta_box(
|
|
61
|
+
'example_order_panel',
|
|
62
|
+
__( 'Shipping desk', 'example-plugin' ),
|
|
63
|
+
'example_render_order_panel',
|
|
64
|
+
wc_get_page_screen_id( 'shop-order' ),
|
|
65
|
+
'side'
|
|
66
|
+
);
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
function example_render_order_panel( $post_or_order ) {
|
|
70
|
+
if ( ! current_user_can( 'edit_shop_orders' ) ) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
$order = $post_or_order instanceof WC_Order ? $post_or_order : wc_get_order( $post_or_order->ID ?? 0 );
|
|
74
|
+
if ( ! $order ) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
wp_nonce_field( 'example_order_panel', 'example_order_nonce' );
|
|
78
|
+
echo esc_html( $order->get_order_number() );
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Sync mode is not compatibility
|
|
83
|
+
|
|
84
|
+
While "Enable compatibility mode" is on, WooCommerce copies orders between the two stores. A plugin can appear to work because it reads the copy. Turn compatibility mode off in a staging shop and repeat the order flow before declaring `custom_order_tables` compatible.
|