lw-elallas-integration
Integrate with or extend "Elállás for WooCommerce"
Install
npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/lw-plugins/lw-elallas-integration
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
git clone https://github.com/Lonsdale201/wp-agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Elállás for WooCommerce — integration & extension
For developers making a plugin/theme work with Elállás for WooCommerce (elallas-for-woo, by uptools.io) — the online right-of-withdrawal (elállási jog) button + case-management plugin for WooCommerce, built for EU Directive 2023/2673 and 415/2025. (XII. 23.) Korm. rendelet. It manages a withdrawal "case" per order in its own tables and fires a small, clean set of elallas_* hooks for integrators.
This is an LW-family plugin — consume hooks, don't edit it
Namespace LightweightPlugins\Elallas, tables wp_lw_elallas_*, and it integrates with LW Site Manager — it's part of the LightweightPlugins family. Integrate by consuming its elallas_* hooks and reading its data; never patch the plugin. If you need an extension point that doesn't exist, request it upstream rather than editing.
Detect it:
if ( defined( 'ELALLAS_FOR_WOO_VERSION' ) ) { /* Elállás for WooCommerce active (1.0.12 tested) */ }
It requires WooCommerce 8.0+ and is HPOS-safe — every hook hands you a real \WC_Order, so use $order->get_*() (never post meta) — see wc-hpos-compatibility.
Extension point 1 — eligibility, deadline, B2B & order numbers
The first four filters decide whether an order may start a withdrawal and how the deadline is computed; they receive the \WC_Order. elallas_resolve_order_number runs earlier, before the order exists, and maps the customer-entered display order number to a WooCommerce order ID.
// Final say on eligibility (runs after the built-in checks).
add_filter( 'elallas_is_order_eligible', function ( bool $eligible, \WC_Order $order ): bool {
// e.g. never allow withdrawal for a "digital-only" order:
return $eligible && ! my_order_is_digital_only( $order );
}, 10, 2 );
// Override the withdrawal window (default: option 'deadline_days', 14).
add_filter( 'elallas_deadline_days', function ( int $days, \WC_Order $order ): int {
return my_is_extended_returns_member( $order ) ? 30 : $days;
}, 10, 2 );
// Override the B2B/B2C heuristic (default: true if company name OR VAT number is set).
add_filter( 'elallas_is_order_b2b', function ( bool $is_b2b, \WC_Order $order ): bool {
return $is_b2b || my_customer_is_business( $order->get_customer_id() );
}, 10, 2 );
// Override the delivery date used as a deadline basis.
add_filter( 'elallas_delivery_date', function ( $delivery, \WC_Order $order ) {
return my_carrier_delivered_at( $order ) ?: $delivery;
}, 10, 2 );
// Resolve a customer-entered/display order number from another numbering plugin.
add_filter( 'elallas_resolve_order_number', function ( int $order_id, string $number ): int {
if ( $order_id > 0 ) {
return $order_id;
}
return my_numbering_plugin_find_order_id( $number ) ?: 0; // 0 = let Elállás fall through.
}, 10, 2 );
Verified: elallas_is_order_eligible at EligibilityChecker.php:67; elallas_deadline_days at :119 (default from Options::get('deadline_days', 14)); elallas_is_order_b2b at B2BDetector.php:40 (heuristic: company OR VAT filled); elallas_delivery_date at OrderAdapter.php:166; elallas_resolve_order_number at OrderAdapter.php:65.
Good to know about the built-in eligibility (so your filter composes correctly): by default only orders whose status is in eligible_statuses (default ['processing','completed']) qualify; an order with an already-open case is refused; logged-in users may only act on their own orders (guests fall back to an email match); and an expired deadline does NOT hard-block by default — it's flagged for manual review unless the expired_handling option is set to 'block' (EligibilityChecker.php:32-72). Your elallas_is_order_eligible filter is the final gate, so returning true can re-allow something the built-in checks denied — return $eligible && your_condition to only ever narrow.
Order identification uses the customer-visible order number, not always the internal WooCommerce order ID. In 1.0.12 the plugin first runs elallas_resolve_order_number, then supports WooCommerce Sequential Order Numbers Pro/free via find_order_by_order_number(), and only finally falls back to treating the entered value as the native order ID. The order details button passes $order->get_order_number() and the prefill keeps non-numeric prefixes/suffixes, so compatibility code must not cast the form value with absint().
Extension point 2 — react to the case lifecycle
A withdrawal case is created, optionally confirmed (two-step flow), then moved through admin statuses. Hook these to sync to a CRM, notify, or trigger invoicing/refund workflows.
// A new withdrawal case was submitted.
add_action( 'elallas_case_created', function ( int $case_id, int $order_id ): void {
my_crm_open_return_ticket( $case_id, $order_id );
}, 10, 2 );
// The customer confirmed (two-step flow). Status is now auto_confirmed or manual_review.
add_action( 'elallas_case_confirmed', function ( int $case_id ): void { /* ... */ } );
// Any status transition (admin or system).
add_action( 'elallas_case_status_changed', function ( int $case_id, string $old, string $new, string $message ): void {
if ( 'refund_pending' === $new ) { my_queue_refund( $case_id ); }
if ( '' !== $message ) {
my_crm_add_customer_visible_note( $case_id, $message );
}
}, 10, 4 );
// Fired specifically for invoicing integrations.
add_action( 'elallas_invoicing_case_created', function ( int $case_id, int $order_id ): void { /* ... */ }, 10, 2 );
Verified: elallas_case_created($case_id, $order_id) at CaseService.php:83; elallas_case_confirmed($case_id) at :124; elallas_case_status_changed($case_id, $old_status, $new_status, $message) at :182; elallas_invoicing_case_created at Integrations/Invoicing.php:63.
The fourth $message argument is the optional admin note sent to the customer in the status-update e-mail. It is populated by the admin case detail form; REST and LW Site Manager status updates currently call change_status() without that message, so expect an empty string in automation-triggered transitions.
$case_id is a row ID in the custom wp_lw_elallas_cases table — NOT a post ID. Don't call get_post()/get_post_meta() on it. Read case data through the plugin's repositories/data or the tables (see reference.md). To find cases from an order, read the order meta the plugin writes: _lw_elallas_has_case ('yes'), _lw_elallas_case_ids (array of case IDs), _lw_elallas_deadline_status (CaseService.php:205-213). Treat that meta as read-only — let the plugin write it.
Case statuses (the CaseStatus set)
received → (auto_confirmed if the deadline is within, else manual_review) → admin moves it to accepted / rejected / awaiting_return / goods_received / refund_pending / closed / cancelled. All ten are the valid values elallas_case_status_changed will report; validate against them with the plugin's CaseStatus::is_valid(). Verified at CaseStatus.php:17-26. (Separately, DeadlineStatus is within / expired / unknown.)
Product/category/tag withdrawal exceptions
The plugin can mark products, product categories and product tags as excluded from withdrawal using _lw_elallas_excluded ('yes' / 'no') and _lw_elallas_exclusion_reason (reason key). Product-level meta wins first; otherwise ProductExclusion::evaluate() checks product_cat and product_tag term meta. Valid reason keys are unsealed, custom, digital, service, hygiene, perishable, sealed.
This does not auto-block the withdrawal flow. OrderSnapshotBuilder writes eligibility_flag = 'excepted' and eligibility_note = <reason label> into the case-item snapshot so the admin notification, case detail and order panel can flag it for manual review. If your integration imports products or bulk-edits exclusions, use the same meta keys via normal WP meta APIs, keep reason keys in the known set, and let the plugin build the snapshot.
Extension point 3 — customize the PDF & emails
// Filter the withdrawal PDF HTML before rendering (dompdf).
add_filter( 'elallas_pdf_html', function ( string $html, array $context ): string {
return str_replace( '{{my_token}}', esc_html( my_value() ), $html );
}, 10, 2 );
Verified at PdfRenderer.php:40. The plugin's emails render through the standard woocommerce_email_header / woocommerce_email_footer hooks, so your existing WC email customizations apply — see wc-emails-classic.
PDF rendering uses the scoped LightweightPlugins\Elallas\Vendor\Dompdf\Dompdf class in release builds and falls back to global \Dompdf\Dompdf only for Composer-dependency installs. A PDF failure is logged and returns an empty string; it should not break case creation or e-mail sending.
Extension point 4 — multilingual output paths
The plugin detects WPML, Polylang and TranslatePress, but the important integration detail is runtime translation, not raw option reads. Admin-entered user-facing strings (button_label, confirm_label, legal_declaration, legal_confirmation, email_customer_extra) are explicitly registered/looked up for WPML and Polylang under the elallas-for-woo string context and printed through Multilingual::translate_option_string() / translate_string() on every output path; TranslatePress can translate the rendered output.
Do not add those option keys as WPML <admin-texts> in a compatibility layer. The plugin's wpml-config.xml intentionally declares only the withdrawal-exception product/term meta as copy and the [elallas_button] label shortcode attribute as translatable. Declaring the options too would double-register strings and fight the runtime translation path.
The stored withdrawal_page_id is resolved through Multilingual::object_id() so [elallas_button], the WooCommerce order button and front-end asset loading target the translated withdrawal page. Case submissions store the WPML/Polylang language code, and customer e-mails / status e-mails / PDFs switch to that case language while rendering; admin notifications switch to the shop default language.
Extension point 5 — elallas_boot
add_action( 'elallas_boot', function ( $plugin ): void {
// Runs once the plugin has booted — safe place to wire your integration.
}, 10, 1 );
Verified at Plugin.php:64.
LW Site Manager abilities it exposes
If LW Site Manager is active, the plugin registers a elallas ability category with elallas/get-case, elallas/list-cases, elallas/update-case-status, and elallas/get-audit-log (via lw_site_manager_register_abilities / lw_site_manager_register_categories, with a fallback direct registration on wp_abilities_api_init). So agents/automation can read and drive cases through Site Manager without touching the tables. Verified at SiteManager/Integration.php:34-39. See lw-site-manager-overview and wp-abilities-api.
WooCommerce logging
The plugin writes to WooCommerce -> Status -> Logs with source elallas-for-woo. warning/error are always written; notice/info/debug require the "Debug logging" option. Context is scrubbed for common PII keys, so integration code should log only identifiers (case_id, order_id, display order_number, status, exception class), never email, IP, user-agent, names, notes or bank-account data.
Critical rules
- Never edit the plugin. Consume
elallas_*hooks; request missing extension points upstream. (LW-family rule.) - Detect with
defined('ELALLAS_FOR_WOO_VERSION')and degrade gracefully when absent. - Customer-entered order numbers are display numbers. Support custom order numbering through
elallas_resolve_order_number; neverabsint()the public form value. elallas_case_status_changedhas 4 args in 1.0.12+ — register accepted args as4if you need the customer-visible status note.$case_idis a custom-table row ID, not a post. Use the plugin's data layer / tables, notget_post_meta.elallas_is_order_eligibleis the final gate — compose with$eligible && your_conditionto narrow; returning baretruecan re-enable orders the built-in checks (status, open case, ownership, expired-blocked) denied.- Order meta (
_lw_elallas_*) is read-only for you — the plugin owns those writes; read to discover cases, don't set them. - Withdrawal-exception product/term meta is configuration, not a denial decision — it flags case items as
excepted; final decision remains in the case workflow. - HPOS-safe: work with the passed
\WC_Ordervia getters, never post meta. - Multilingual strings are runtime-translated — do not read/store raw option strings for customer output and do not duplicate them as WPML admin-texts.
- Case statuses are the
CaseStatusenum (ten values) — don't invent statuses; validate withCaseStatus::is_valid(). - B2B detection is a heuristic (company/VAT presence) the merchant overrides per case — treat
elallas_is_order_b2bas advisory, not authoritative legal classification.
Common mistakes
// WRONG — treating the case id as a post
$note = get_post_meta( $case_id, 'note', true ); // case_id is a wp_lw_elallas_cases row id, not a post
// WRONG — re-allowing everything (ignores built-in eligibility)
add_filter( 'elallas_is_order_eligible', '__return_true' ); // bypasses status/deadline/ownership checks
// RIGHT — only narrow
add_filter( 'elallas_is_order_eligible', fn( $ok, $order ) => $ok && my_extra_check( $order ), 10, 2 );
// WRONG — writing the plugin's order meta yourself
$order->update_meta_data( '_lw_elallas_has_case', 'yes' ); // the plugin owns this write
// WRONG — breaking stores with custom/sequential order numbers
$order_id = absint( $_POST['order_number'] ?? 0 ); // customer sees display number, not always WC ID
// RIGHT — let Elállás or your filter resolve the display number
add_filter( 'elallas_resolve_order_number', 'my_resolve_display_order_number', 10, 2 );
Cross-references
wc-hpos-compatibility— the plugin is HPOS-safe; keep your integration HPOS-safe too.wc-sequential-order-numbers-pro— display order numbers must resolve before the withdrawal flow can identify an order.wc-order-lifecycle-and-items— for reacting to the underlying WooCommerce order/refund side.lw-site-manager-overview/wp-abilities-api— theelallas/*abilities it exposes.wpml-string-translation/wpml-config— understand the runtime string registration and the intentionalwpml-config.xmlscope.- See
reference.mdfor the full hook table, the 4-table schema, the order meta keys, and the status/options enums.
What this skill does NOT cover
- The plugin's admin UI / case workflow as an end user — this is a developer-integration skill.
- The legal/compliance interpretation of Directive 2023/2673 or 415/2025 Korm. rendelet — that's the plugin's domain, not this skill's.
- Editing the plugin's internals — out of scope by design (consume hooks).
- The withdrawal-form Gutenberg block internals (
blocks/withdrawal-form) — beyond noting it exists.
References
- Eligibility/deadline: includes/Domain/EligibilityChecker.php (
elallas_is_order_eligible67,elallas_deadline_days119, default eligible statuses 81). - B2B: includes/Domain/B2BDetector.php:40.
- Product/category/tag exceptions: includes/Admin/ProductFields.php, includes/Admin/TermFields.php, includes/Domain/ProductExclusion.php, includes/Domain/OrderSnapshotBuilder.php.
- Order numbers/delivery: includes/Woo/OrderAdapter.php (
elallas_resolve_order_number65, Sequential helper lookup 74-85,elallas_delivery_date166). - Case lifecycle + order meta: includes/Domain/CaseService.php (created 83, confirmed 124, status_changed 182, order meta 205-213).
- Case statuses: includes/Models/CaseStatus.php:17-26; deadline statuses:
includes/Models/DeadlineStatus.php:17-19. - Multilingual: includes/Integrations/Multilingual.php, wpml-config.xml, includes/Frontend/Shortcodes.php, includes/Frontend/SubmissionContext.php, includes/Emails/EmailManager.php.
- PDF filter/logging: includes/Pdf/PdfRenderer.php:40, includes/Support/Logger.php; invoicing: includes/Integrations/Invoicing.php:63; boot: includes/Plugin.php:64.
- Tables + Site Manager abilities: includes/Database/Schema.php, includes/SiteManager/Integration.php:34-39.
- Official documentation: https://developer.woocommerce.com/docs/features/high-performance-order-storage/
- Verified source paths:
wp-content/plugins/elallas-for-woo/elallas-for-woo.phpwp-content/plugins/elallas-for-woo/includes/Woo/Hooks.php
Files (wp-agent-skills)
-
reference.md 10.7 KB
# lw-elallas-integration — reference Full extension surface of **Elállás for WooCommerce** (`elallas-for-woo` 1.0.12, `LightweightPlugins\Elallas`). Integrate by consuming these hooks / reading these tables — never by editing the plugin. ## All `elallas_*` hooks | Hook | Type | Signature | Fires / purpose | Source | |---|---|---|---|---| | `elallas_boot` | action | `($plugin)` | After the plugin boots — wire your integration here | Plugin.php:64 | | `elallas_is_order_eligible` | filter | `(bool $eligible, \WC_Order $order)` | Final gate on whether an order may start a withdrawal | EligibilityChecker.php:67 | | `elallas_deadline_days` | filter | `(int $days, \WC_Order $order)` | The withdrawal window in days (default `deadline_days` option = 14) | EligibilityChecker.php:119 | | `elallas_is_order_b2b` | filter | `(bool $is_b2b, \WC_Order $order)` | B2B/B2C classification (default: company OR VAT present) | B2BDetector.php:40 | | `elallas_resolve_order_number` | filter | `(int $order_id, string $number)` | Resolve a customer-entered/display order number to a WooCommerce order ID; return `0` to fall through | Woo/OrderAdapter.php:65 | | `elallas_delivery_date` | filter | `(mixed $delivery, \WC_Order $order)` | Delivery date used as a deadline basis | Woo/OrderAdapter.php:166 | | `elallas_case_created` | action | `(int $case_id, int $order_id)` | A withdrawal case was created (status `received`) | Domain/CaseService.php:83 | | `elallas_case_confirmed` | action | `(int $case_id)` | Customer confirmed (two-step); status now `auto_confirmed` or `manual_review` | Domain/CaseService.php:124 | | `elallas_case_status_changed` | action | `(int $case_id, string $old_status, string $new_status, string $message)` | Any status transition (admin/system), optionally with a customer-visible admin note | Domain/CaseService.php:182 | | `elallas_invoicing_case_created` | action | `(int $case_id, int $order_id)` | Invoicing-integration entry point | Integrations/Invoicing.php:63 | | `elallas_pdf_html` | filter | `(string $html, array $context)` | Withdrawal PDF HTML before dompdf render | Pdf/PdfRenderer.php:40 | It also fires the standard WooCommerce email hooks (`woocommerce_email_header` / `woocommerce_email_footer` / `_footer_text`) and uses WPML/Polylang runtime string translation for admin-entered customer-facing strings. ## Order number resolution `OrderAdapter::get_order_by_number()` resolves the order number the customer sees and types. In 1.0.12 the order-details button uses `$order->get_order_number()`, and the identify prefill keeps prefixes/suffixes instead of casting to an integer. Resolution order: 1. `elallas_resolve_order_number` filter (`0` means unresolved / fall through). 2. WooCommerce Sequential Order Numbers Pro/free helpers: `wc_seq_order_number_pro()` or `wc_seq_order_number()` with `find_order_by_order_number()`. 3. Native WooCommerce order ID fallback by stripping non-digits. Compatibility rule: if another order-numbering plugin is present, hook `elallas_resolve_order_number`; do not rely on `absint( $number )` anywhere customer input is involved. ## Case lifecycle (CaseStatus) Values (`includes/Models/CaseStatus.php:17-26`) — validate with `CaseStatus::is_valid()`, label with `CaseStatus::label()`: ``` received # created auto_confirmed # confirmed while deadline is 'within' manual_review # confirmed while deadline is expired/unknown accepted # admin rejected # admin awaiting_return # admin — waiting for goods back goods_received # admin refund_pending # admin — refund due closed # admin — terminal cancelled # admin/customer — terminal ``` Flow: `received` → (`auto_confirmed` | `manual_review`) → admin moves to any of `accepted`/`rejected`/`awaiting_return`/`goods_received`/`refund_pending`/`closed`/`cancelled`. `DeadlineStatus` (separate, `includes/Models/DeadlineStatus.php:17-19`): `within` / `expired` / `unknown`. `elallas_case_status_changed` passes a fourth `$message` argument. The admin case detail form can populate it as a note sent to the customer in the status-update e-mail. REST and LW Site Manager status updates currently do not pass this message, so integrations must tolerate an empty string. ## Order meta the plugin writes (read-only for integrators) Set in `CaseService::update_order_meta()` (CaseService.php:205-213). Read to discover cases from an order; do not write these yourself. | Meta key | Value | |---|---| | `_lw_elallas_has_case` | `'yes'` once a case exists | | `_lw_elallas_case_ids` | array of case IDs (ints) | | `_lw_elallas_deadline_status` | `within` / `expired` / `unknown` | ## Custom tables (schema from includes/Database/Schema.php) All prefixed `{$wpdb->prefix}lw_elallas_`. `case_id` is a row id in `..._cases`, **not** a post id. ### `lw_elallas_cases` (Schema.php:66-97) `id`, `case_number` (UNIQUE), `order_id`, `order_number`, `customer_id`, `customer_email_hash` CHAR(64), `customer_email_encrypted` TEXT, `status` (default `received`), `withdrawal_type` (`full`/…), `submitted_at`, `confirmed_at`, `deadline_status` (default `unknown`), `order_created_at`, `order_completed_at`, `delivery_date`, `ip_hash`, `user_agent_hash`, `source_url`, `language`, `assigned_admin_id`, `customer_note` TEXT, `bank_account_encrypted` TEXT, `created_at`, `updated_at`. Keys on `order_id`, `customer_id`, `status`, `deadline_status`. **Privacy:** email, bank account, IP and user-agent are **hashed/encrypted at rest** — there is no plaintext email column (`customer_email_hash` + `customer_email_encrypted`). Don't expect to `SELECT` a readable email; go through the plugin's data layer if it exposes decryption, or match on the hash. ### `lw_elallas_case_items` (Schema.php:109-126) `id`, `case_id`, `order_item_id`, `product_id`, `variation_id`, `product_name_snapshot`, `sku_snapshot`, `qty_ordered`, `qty_withdrawn`, `line_total_snapshot` DECIMAL(19,4), `tax_total_snapshot`, `eligibility_flag` (default `eligible`), `eligibility_note`. Snapshots are captured at case creation — they don't change if the product later changes. ### `lw_elallas_events` (audit log, Schema.php:137-149) `id`, `case_id`, `event_type` (e.g. `case_created`, `case_confirmed`, `status_changed`), `actor_type` (`system`/`admin`/`customer`), `actor_id`, `message` TEXT, `metadata_json` LONGTEXT, `created_at`. Every lifecycle step logs a row here (`EventRepository::log`). ### `lw_elallas_documents` (Schema.php:160-170) `id`, `case_id`, `document_type` (default `withdrawal_statement`), `file_path`, `file_hash` CHAR(64), `token`, `created_at`. ## Product/category/tag withdrawal exceptions Configured through product meta and product-category/product-tag term meta: | Meta key | Scope | Value | |---|---|---| | `_lw_elallas_excluded` | product, `product_cat`, `product_tag` | `'yes'` / `'no'` | | `_lw_elallas_exclusion_reason` | product, `product_cat`, `product_tag` | `unsealed`, `custom`, `digital`, `service`, `hygiene`, `perishable`, `sealed`, or `''` | Resolution: product meta wins first, then `ProductExclusion::taxonomy_exclusion()` checks `product_cat` and `product_tag` term meta. Matching items are written into `lw_elallas_case_items` with `eligibility_flag = 'excepted'` and the reason label in `eligibility_note`. Important: an exception flag does not auto-block submission. It marks the item/case for manual review and powers admin/e-mail warnings. ## LW Site Manager abilities (category `elallas`) Registered in `includes/SiteManager/Integration.php:34-39` on `lw_site_manager_register_categories` + `lw_site_manager_register_abilities`, with a fallback direct registration on `wp_abilities_api_init` (priority 20) so the abilities exist even if Site Manager's own hooks didn't fire: - `elallas/get-case` - `elallas/list-cases` - `elallas/update-case-status` - `elallas/get-audit-log` Prefer these (via LW Site Manager / the WP Abilities API — see `lw-site-manager-overview`, `wp-abilities-api`) over direct table access when you need to read or drive cases programmatically. `elallas/update-case-status` accepts `case_id` and `status` only. It drives `Domain\CaseService::change_status()` but does not currently expose the customer-visible status message. ## Multilingual behavior The multilingual integration is centralized in `includes/Integrations/Multilingual.php`: - Registers option strings `legal_declaration`, `legal_confirmation`, `button_label`, `confirm_label`, `email_customer_extra` under context `elallas-for-woo`. - Translates output via `Multilingual::translate_option_string()` / `translate_string()`; output paths should not print raw option values. - Resolves translated page IDs via `Multilingual::object_id()`, so `[elallas_button]`, the order button and asset loading target the translated withdrawal page. - Stores the submission language using the WPML/Polylang language code where available, falling back to locale. - Switches WPML language while rendering customer confirmation e-mails, status e-mails and PDFs; admin notification switches to the default shop language. `wpml-config.xml` intentionally declares only: - `_lw_elallas_excluded` and `_lw_elallas_exclusion_reason` product/term meta as `copy`. - The `[elallas_button]` `label` shortcode attribute as translatable. Do not add the admin-entered option strings as WPML `<admin-texts>` in another compatibility layer; they are runtime-registered so WPML and Polylang share the same output path and background renders can switch language explicitly. ## WooCommerce logging `Support\Logger` writes to WooCommerce logs with source `elallas-for-woo`. - `error` / `warning` are always written. - `notice` / `info` / `debug` require the `logging_enabled` option. - Context is scrubbed for common PII keys (`email`, `customer_email`, `ip`, `user_agent`, `name`, `bank_account`, `customer_note`, etc.). Use IDs and status values in integration logs; never pass plaintext customer data into the logger context. ## Built-in eligibility (so your `elallas_is_order_eligible` filter composes correctly) From `EligibilityChecker::check()` (EligibilityChecker.php:32-72), an order is denied by default when any of these hold — your filter runs last and can only be trusted to *narrow* if you `&&` your condition: - Order status not in `eligible_statuses` option (default `['processing','completed']`). - An open case already exists for the order. - A logged-in user is acting on an order that isn't theirs (guests must match the order email). - Deadline `expired` AND `expired_handling` option = `'block'` (otherwise expiry only flags manual review, never hard-blocks). ## Detection & requirements - `defined( 'ELALLAS_FOR_WOO_VERSION' )` — active check (constant `'1.0.12'`, `elallas-for-woo.php:31`). - Namespace `LightweightPlugins\Elallas`; requires WooCommerce 8.0+, PHP 8.0+, WP 6.4+. - HPOS-safe — hooks hand you a `\WC_Order`; use getters, not post meta. -
SKILL.md 18.1 KB
--- name: lw-elallas-integration description: Integrate with or extend "Elállás for WooCommerce" (elallas-for-woo), the LW-family EU/HU withdrawal case-management plugin for WooCommerce. Covers its elallas_* hooks, eligibility/deadline/B2B /delivery/order-number filters (including elallas_resolve_order_number), the 4-arg elallas_case_status_changed lifecycle action, PDF/email hooks, multilingual WPML/Polylang output, custom tables/order meta, WooCommerce logging, and LW Site Manager abilities. Use when building withdrawal-case compatibility, syncing cases to CRM/invoicing, supporting custom order numbers, or customizing its PDF/emails without editing the plugin. metadata: wp-skills-author: "Soczó Kristóf" wp-skills-contact: "mailto:lonsdale201@hotmail.com" wp-skills-plugin: "elallas-for-woo" wp-skills-plugin-version-tested: "1.0.12" wp-skills-php-min: "8.0" wp-skills-last-updated: "2026-07-09" --- # Elállás for WooCommerce — integration & extension For developers making a plugin/theme work with **Elállás for WooCommerce** (`elallas-for-woo`, by uptools.io) — the online right-of-withdrawal (elállási jog) button + case-management plugin for WooCommerce, built for EU Directive 2023/2673 and 415/2025. (XII. 23.) Korm. rendelet. It manages a **withdrawal "case"** per order in its own tables and fires a small, clean set of `elallas_*` hooks for integrators. ## This is an LW-family plugin — consume hooks, don't edit it Namespace `LightweightPlugins\Elallas`, tables `wp_lw_elallas_*`, and it integrates with LW Site Manager — it's part of the LightweightPlugins family. **Integrate by consuming its `elallas_*` hooks and reading its data; never patch the plugin.** If you need an extension point that doesn't exist, request it upstream rather than editing. Detect it: ```php if ( defined( 'ELALLAS_FOR_WOO_VERSION' ) ) { /* Elállás for WooCommerce active (1.0.12 tested) */ } ``` It requires WooCommerce 8.0+ and is HPOS-safe — every hook hands you a real `\WC_Order`, so use `$order->get_*()` (never post meta) — see `wc-hpos-compatibility`. ## Extension point 1 — eligibility, deadline, B2B & order numbers The first four filters decide **whether an order may start a withdrawal** and how the deadline is computed; they receive the `\WC_Order`. `elallas_resolve_order_number` runs earlier, before the order exists, and maps the customer-entered display order number to a WooCommerce order ID. ```php // Final say on eligibility (runs after the built-in checks). add_filter( 'elallas_is_order_eligible', function ( bool $eligible, \WC_Order $order ): bool { // e.g. never allow withdrawal for a "digital-only" order: return $eligible && ! my_order_is_digital_only( $order ); }, 10, 2 ); // Override the withdrawal window (default: option 'deadline_days', 14). add_filter( 'elallas_deadline_days', function ( int $days, \WC_Order $order ): int { return my_is_extended_returns_member( $order ) ? 30 : $days; }, 10, 2 ); // Override the B2B/B2C heuristic (default: true if company name OR VAT number is set). add_filter( 'elallas_is_order_b2b', function ( bool $is_b2b, \WC_Order $order ): bool { return $is_b2b || my_customer_is_business( $order->get_customer_id() ); }, 10, 2 ); // Override the delivery date used as a deadline basis. add_filter( 'elallas_delivery_date', function ( $delivery, \WC_Order $order ) { return my_carrier_delivered_at( $order ) ?: $delivery; }, 10, 2 ); // Resolve a customer-entered/display order number from another numbering plugin. add_filter( 'elallas_resolve_order_number', function ( int $order_id, string $number ): int { if ( $order_id > 0 ) { return $order_id; } return my_numbering_plugin_find_order_id( $number ) ?: 0; // 0 = let Elállás fall through. }, 10, 2 ); ``` Verified: `elallas_is_order_eligible` at [EligibilityChecker.php:67](EligibilityChecker.php); `elallas_deadline_days` at [:119](EligibilityChecker.php) (default from `Options::get('deadline_days', 14)`); `elallas_is_order_b2b` at [B2BDetector.php:40](B2BDetector.php) (heuristic: company OR VAT filled); `elallas_delivery_date` at [OrderAdapter.php:166](OrderAdapter.php); `elallas_resolve_order_number` at [OrderAdapter.php:65](OrderAdapter.php). Good to know about the built-in eligibility (so your filter composes correctly): by default only orders whose status is in `eligible_statuses` (default `['processing','completed']`) qualify; an order with an already-open case is refused; logged-in users may only act on their own orders (guests fall back to an email match); and **an expired deadline does NOT hard-block by default** — it's flagged for manual review unless the `expired_handling` option is set to `'block'` ([EligibilityChecker.php:32-72](EligibilityChecker.php)). Your `elallas_is_order_eligible` filter is the final gate, so returning `true` can re-allow something the built-in checks denied — return `$eligible && your_condition` to only ever narrow. Order identification uses the customer-visible order number, not always the internal WooCommerce order ID. In 1.0.12 the plugin first runs `elallas_resolve_order_number`, then supports WooCommerce Sequential Order Numbers Pro/free via `find_order_by_order_number()`, and only finally falls back to treating the entered value as the native order ID. The order details button passes `$order->get_order_number()` and the prefill keeps non-numeric prefixes/suffixes, so compatibility code must not cast the form value with `absint()`. ## Extension point 2 — react to the case lifecycle A withdrawal **case** is created, optionally confirmed (two-step flow), then moved through admin statuses. Hook these to sync to a CRM, notify, or trigger invoicing/refund workflows. ```php // A new withdrawal case was submitted. add_action( 'elallas_case_created', function ( int $case_id, int $order_id ): void { my_crm_open_return_ticket( $case_id, $order_id ); }, 10, 2 ); // The customer confirmed (two-step flow). Status is now auto_confirmed or manual_review. add_action( 'elallas_case_confirmed', function ( int $case_id ): void { /* ... */ } ); // Any status transition (admin or system). add_action( 'elallas_case_status_changed', function ( int $case_id, string $old, string $new, string $message ): void { if ( 'refund_pending' === $new ) { my_queue_refund( $case_id ); } if ( '' !== $message ) { my_crm_add_customer_visible_note( $case_id, $message ); } }, 10, 4 ); // Fired specifically for invoicing integrations. add_action( 'elallas_invoicing_case_created', function ( int $case_id, int $order_id ): void { /* ... */ }, 10, 2 ); ``` Verified: `elallas_case_created($case_id, $order_id)` at [CaseService.php:83](CaseService.php); `elallas_case_confirmed($case_id)` at [:124](CaseService.php); `elallas_case_status_changed($case_id, $old_status, $new_status, $message)` at [:182](CaseService.php); `elallas_invoicing_case_created` at [Integrations/Invoicing.php:63](Invoicing.php). The fourth `$message` argument is the optional admin note sent to the customer in the status-update e-mail. It is populated by the admin case detail form; REST and LW Site Manager status updates currently call `change_status()` without that message, so expect an empty string in automation-triggered transitions. **`$case_id` is a row ID in the custom `wp_lw_elallas_cases` table — NOT a post ID.** Don't call `get_post()`/`get_post_meta()` on it. Read case data through the plugin's repositories/data or the tables (see reference.md). To find cases from an order, read the order meta the plugin writes: `_lw_elallas_has_case` (`'yes'`), `_lw_elallas_case_ids` (array of case IDs), `_lw_elallas_deadline_status` ([CaseService.php:205-213](CaseService.php)). Treat that meta as read-only — let the plugin write it. ### Case statuses (the `CaseStatus` set) `received` → (`auto_confirmed` if the deadline is `within`, else `manual_review`) → admin moves it to `accepted` / `rejected` / `awaiting_return` / `goods_received` / `refund_pending` / `closed` / `cancelled`. All ten are the valid values `elallas_case_status_changed` will report; validate against them with the plugin's `CaseStatus::is_valid()`. Verified at [CaseStatus.php:17-26](CaseStatus.php). (Separately, `DeadlineStatus` is `within` / `expired` / `unknown`.) ## Product/category/tag withdrawal exceptions The plugin can mark products, product categories and product tags as excluded from withdrawal using `_lw_elallas_excluded` (`'yes'` / `'no'`) and `_lw_elallas_exclusion_reason` (reason key). Product-level meta wins first; otherwise `ProductExclusion::evaluate()` checks `product_cat` and `product_tag` term meta. Valid reason keys are `unsealed`, `custom`, `digital`, `service`, `hygiene`, `perishable`, `sealed`. This does **not** auto-block the withdrawal flow. `OrderSnapshotBuilder` writes `eligibility_flag = 'excepted'` and `eligibility_note = <reason label>` into the case-item snapshot so the admin notification, case detail and order panel can flag it for manual review. If your integration imports products or bulk-edits exclusions, use the same meta keys via normal WP meta APIs, keep reason keys in the known set, and let the plugin build the snapshot. ## Extension point 3 — customize the PDF & emails ```php // Filter the withdrawal PDF HTML before rendering (dompdf). add_filter( 'elallas_pdf_html', function ( string $html, array $context ): string { return str_replace( '{{my_token}}', esc_html( my_value() ), $html ); }, 10, 2 ); ``` Verified at [PdfRenderer.php:40](PdfRenderer.php). The plugin's emails render through the standard `woocommerce_email_header` / `woocommerce_email_footer` hooks, so your existing WC email customizations apply — see `wc-emails-classic`. PDF rendering uses the scoped `LightweightPlugins\Elallas\Vendor\Dompdf\Dompdf` class in release builds and falls back to global `\Dompdf\Dompdf` only for Composer-dependency installs. A PDF failure is logged and returns an empty string; it should not break case creation or e-mail sending. ## Extension point 4 — multilingual output paths The plugin detects WPML, Polylang and TranslatePress, but the important integration detail is **runtime translation**, not raw option reads. Admin-entered user-facing strings (`button_label`, `confirm_label`, `legal_declaration`, `legal_confirmation`, `email_customer_extra`) are explicitly registered/looked up for WPML and Polylang under the `elallas-for-woo` string context and printed through `Multilingual::translate_option_string()` / `translate_string()` on every output path; TranslatePress can translate the rendered output. Do not add those option keys as WPML `<admin-texts>` in a compatibility layer. The plugin's `wpml-config.xml` intentionally declares only the withdrawal-exception product/term meta as `copy` and the `[elallas_button]` `label` shortcode attribute as translatable. Declaring the options too would double-register strings and fight the runtime translation path. The stored `withdrawal_page_id` is resolved through `Multilingual::object_id()` so `[elallas_button]`, the WooCommerce order button and front-end asset loading target the translated withdrawal page. Case submissions store the WPML/Polylang language code, and customer e-mails / status e-mails / PDFs switch to that case language while rendering; admin notifications switch to the shop default language. ## Extension point 5 — `elallas_boot` ```php add_action( 'elallas_boot', function ( $plugin ): void { // Runs once the plugin has booted — safe place to wire your integration. }, 10, 1 ); ``` Verified at [Plugin.php:64](Plugin.php). ## LW Site Manager abilities it exposes If LW Site Manager is active, the plugin registers a `elallas` ability category with `elallas/get-case`, `elallas/list-cases`, `elallas/update-case-status`, and `elallas/get-audit-log` (via `lw_site_manager_register_abilities` / `lw_site_manager_register_categories`, with a fallback direct registration on `wp_abilities_api_init`). So agents/automation can read and drive cases through Site Manager without touching the tables. Verified at [SiteManager/Integration.php:34-39](Integration.php). See `lw-site-manager-overview` and `wp-abilities-api`. ## WooCommerce logging The plugin writes to WooCommerce -> Status -> Logs with source `elallas-for-woo`. `warning`/`error` are always written; `notice`/`info`/`debug` require the "Debug logging" option. Context is scrubbed for common PII keys, so integration code should log only identifiers (`case_id`, `order_id`, display `order_number`, status, exception class), never email, IP, user-agent, names, notes or bank-account data. ## Critical rules - **Never edit the plugin.** Consume `elallas_*` hooks; request missing extension points upstream. (LW-family rule.) - **Detect with `defined('ELALLAS_FOR_WOO_VERSION')`** and degrade gracefully when absent. - **Customer-entered order numbers are display numbers.** Support custom order numbering through `elallas_resolve_order_number`; never `absint()` the public form value. - **`elallas_case_status_changed` has 4 args in 1.0.12+** — register accepted args as `4` if you need the customer-visible status note. - **`$case_id` is a custom-table row ID, not a post.** Use the plugin's data layer / tables, not `get_post_meta`. - **`elallas_is_order_eligible` is the final gate** — compose with `$eligible && your_condition` to narrow; returning bare `true` can re-enable orders the built-in checks (status, open case, ownership, expired-blocked) denied. - **Order meta (`_lw_elallas_*`) is read-only for you** — the plugin owns those writes; read to discover cases, don't set them. - **Withdrawal-exception product/term meta is configuration, not a denial decision** — it flags case items as `excepted`; final decision remains in the case workflow. - **HPOS-safe**: work with the passed `\WC_Order` via getters, never post meta. - **Multilingual strings are runtime-translated** — do not read/store raw option strings for customer output and do not duplicate them as WPML admin-texts. - **Case statuses are the `CaseStatus` enum** (ten values) — don't invent statuses; validate with `CaseStatus::is_valid()`. - **B2B detection is a heuristic** (company/VAT presence) the merchant overrides per case — treat `elallas_is_order_b2b` as advisory, not authoritative legal classification. ## Common mistakes ```php // WRONG — treating the case id as a post $note = get_post_meta( $case_id, 'note', true ); // case_id is a wp_lw_elallas_cases row id, not a post // WRONG — re-allowing everything (ignores built-in eligibility) add_filter( 'elallas_is_order_eligible', '__return_true' ); // bypasses status/deadline/ownership checks // RIGHT — only narrow add_filter( 'elallas_is_order_eligible', fn( $ok, $order ) => $ok && my_extra_check( $order ), 10, 2 ); // WRONG — writing the plugin's order meta yourself $order->update_meta_data( '_lw_elallas_has_case', 'yes' ); // the plugin owns this write // WRONG — breaking stores with custom/sequential order numbers $order_id = absint( $_POST['order_number'] ?? 0 ); // customer sees display number, not always WC ID // RIGHT — let Elállás or your filter resolve the display number add_filter( 'elallas_resolve_order_number', 'my_resolve_display_order_number', 10, 2 ); ``` ## Cross-references - **`wc-hpos-compatibility`** — the plugin is HPOS-safe; keep your integration HPOS-safe too. - **`wc-sequential-order-numbers-pro`** — display order numbers must resolve before the withdrawal flow can identify an order. - **`wc-order-lifecycle-and-items`** — for reacting to the underlying WooCommerce order/refund side. - **`lw-site-manager-overview`** / **`wp-abilities-api`** — the `elallas/*` abilities it exposes. - **`wpml-string-translation`** / **`wpml-config`** — understand the runtime string registration and the intentional `wpml-config.xml` scope. - See `reference.md` for the full hook table, the 4-table schema, the order meta keys, and the status/options enums. ## What this skill does NOT cover - **The plugin's admin UI / case workflow as an end user** — this is a developer-integration skill. - **The legal/compliance interpretation** of Directive 2023/2673 or 415/2025 Korm. rendelet — that's the plugin's domain, not this skill's. - **Editing the plugin's internals** — out of scope by design (consume hooks). - **The withdrawal-form Gutenberg block internals** (`blocks/withdrawal-form`) — beyond noting it exists. ## References - Eligibility/deadline: [includes/Domain/EligibilityChecker.php](EligibilityChecker.php) (`elallas_is_order_eligible` 67, `elallas_deadline_days` 119, default eligible statuses 81). - B2B: [includes/Domain/B2BDetector.php:40](B2BDetector.php). - Product/category/tag exceptions: [includes/Admin/ProductFields.php](ProductFields.php), [includes/Admin/TermFields.php](TermFields.php), [includes/Domain/ProductExclusion.php](ProductExclusion.php), [includes/Domain/OrderSnapshotBuilder.php](OrderSnapshotBuilder.php). - Order numbers/delivery: [includes/Woo/OrderAdapter.php](OrderAdapter.php) (`elallas_resolve_order_number` 65, Sequential helper lookup 74-85, `elallas_delivery_date` 166). - Case lifecycle + order meta: [includes/Domain/CaseService.php](CaseService.php) (created 83, confirmed 124, status_changed 182, order meta 205-213). - Case statuses: [includes/Models/CaseStatus.php:17-26](CaseStatus.php); deadline statuses: `includes/Models/DeadlineStatus.php:17-19`. - Multilingual: [includes/Integrations/Multilingual.php](Multilingual.php), [wpml-config.xml](wpml-config.xml), [includes/Frontend/Shortcodes.php](Shortcodes.php), [includes/Frontend/SubmissionContext.php](SubmissionContext.php), [includes/Emails/EmailManager.php](EmailManager.php). - PDF filter/logging: [includes/Pdf/PdfRenderer.php:40](PdfRenderer.php), [includes/Support/Logger.php](Logger.php); invoicing: [includes/Integrations/Invoicing.php:63](Invoicing.php); boot: [includes/Plugin.php:64](Plugin.php). - Tables + Site Manager abilities: [includes/Database/Schema.php](Schema.php), [includes/SiteManager/Integration.php:34-39](Integration.php). - Official documentation: <https://developer.woocommerce.com/docs/features/high-performance-order-storage/> - Verified source paths: - `wp-content/plugins/elallas-for-woo/elallas-for-woo.php` - `wp-content/plugins/elallas-for-woo/includes/Woo/Hooks.php`
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.