fluentcart-customers-portal
Implements and audits FluentCart customer identity, WP_User linkage, addresses, ownership checks, account creation, customer-scoped queries, and custom portal endpoints. Use when working with Customer, CustomerResource, getCurrentCustomer(), fct_customers, customer-profile REST r
Install
npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/fluentcart/fluentcart-customers-portal
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
FluentCart customers and portal
Treat Customer as the commerce identity and WP_User as an optional login identity. Enforce ownership for every customer-facing object.
Read customer-identity.md before changing email/user links, moving resources, or exposing customer data.
Resolve identity safely
- Customer rows live in fct_customers and may link to WP users by user_id.
- Orders, subscriptions, addresses, licenses, statistics, and labels reference Customer IDs, not WP user IDs.
- CustomerResource::getCurrentCustomer() first resolves the logged-in WP user by user_id or matching email and may attach that user_id to the existing row.
- Passing true may create a Customer from the current WP user.
- Email matching is a linking operation. Validate verified ownership and duplicate/merge consequences before changing an address.
Use Customers/CustomerResource and the normal resource-moving hooks for coordinated changes. Do not update order and subscription customer_id values piecemeal.
Enforce object ownership
CustomerFrontendPolicy only proves that a WP user is logged in. It does not authorize an arbitrary order, subscription, address, download, or license.
For every custom handler:
- resolve CustomerResource::getCurrentCustomer();
- query the requested object with customer_id equal to that customer;
- reject absent/mismatched objects without revealing whether another customer owns them;
- authorize the requested mutation separately;
- use FluentCart lifecycle methods for the mutation.
Never authorize solely by UUID, license key, address ID, download ID, or portal URL.
Add a portal page
Register at fluent_cart/init or later. Use a unique kebab-case slug that is not dashboard, purchase-history, subscriptions, licenses, downloads, or profile.
fluent_cart_api()->addCustomerDashboardEndpoint('acme-benefits', [
'title' => __('Benefits', 'acme-addon'),
'render_callback' => static function (): void {
$customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
if (!$customer) {
return;
}
echo '<div>' . esc_html__('Your benefits', 'acme-addon') . '</div>';
},
]);
The render callback receives no arguments and echoes content inside the logged-in portal shell. Escape output and recheck any resource ownership inside it. A page_id can be supplied instead, but its content must still avoid exposing unscoped customer data.
Keep caches request-scoped
CustomerResource caches the current customer statically. Call resetCurrentCustomerRuntimeCache() between simulated users/requests in PHPUnit, WP-CLI loops, workers, or multisite switches. Do not let one user's cached Customer bleed into another iteration.
Handle account and email changes
- Do not create duplicate WP users merely because an order is paid; core may create/link an account according to store/order policy.
- Normalize and validate email, then search both Customer and WP_User collision cases.
- Move all connected resources through the normal move/change operation.
- React to fluent_cart/customer_email_changed and fluent_cart/customer_resources_moved only after verifying their payload.
- Recount customer statistics after repair/import, not on every read.
- Never include PII in public cache keys, logs, HTML data attributes, or URLs.
Cross-references
- Use fluentcart-rest-headless for customer-facing endpoints.
- Use fluentcart-orders-transactions for customer order queries.
- Use fluentcart-licensing-pro for Pro license ownership.
References
- Official customer hooks: https://dev.fluentcart.com/hooks/actions/customers-users/
- Verified Free source paths:
- fluent-cart/app/Models/Customer.php
- fluent-cart/api/Customers.php
- fluent-cart/api/Resource/CustomerResource.php
- fluent-cart/api/Resource/CustomerAddressResource.php
- fluent-cart/app/Http/Policies/CustomerFrontendPolicy.php
- fluent-cart/app/Http/Controllers/FrontendControllers/
- fluent-cart/api/FluentCartGeneralApi.php
- fluent-cart/app/Hooks/Handlers/ShortCodes/CustomerProfileHandler.php
- fluent-cart/app/Hooks/Handlers/UserHandler.php
Files (wp-agent-skills)
-
agents
-
openai.yaml 313 B
interface: display_name: "FluentCart customers and portal" short_description: "Protect customer identity, ownership, and portal data" default_prompt: "Use $fluentcart-customers-portal to implement or audit this customer or portal feature with correct Customer/WP_User identity, ownership, and cache scope."
-
-
references
-
customer-identity.md 1.9 KB
# FluentCart 1.6.0 customer identity ## Identity graph ~~~text WP_User (optional login) | user_id or verified email link Customer |-- CustomerAddress rows |-- Orders -> items / transactions / order addresses |-- Subscriptions |-- Download permissions |-- Pro licenses and activations ~~~ Order addresses are snapshots. Updating a customer's primary address must not silently rewrite historical invoice/order addresses. ## Current customer resolution CustomerResource::getCurrentCustomer(): 1. returns null for anonymous requests; 2. loads the current WP_User; 3. queries Customer where user_id matches OR email matches; 4. eager-loads billing/shipping addresses; 5. attaches user_id when an email match has no/different link; 6. optionally creates a Customer; 7. stores the result in a static runtime cache. This makes email uniqueness and account-email changes security-sensitive. ## Ownership query pattern Resolve the current customer first, then include customer_id in the same query that resolves the opaque identifier. Avoid loading by UUID and checking later when a scoped query is possible. Return the same not-found response for absent and foreign objects. Apply capability checks in addition to ownership for privileged/admin functions. ## Resource moves A customer merge/change can affect orders, child renewal orders, transactions, subscriptions, addresses, labels, download permissions, integrations, and Pro licenses. Use source-verified core movement logic and listen to customer_resources_moved for addon-owned foreign rows. Make the addon callback transactional or resumable and idempotent by from/to customer IDs. ## Test matrix Test anonymous, linked WP user, email-only legacy customer, email collision, customer with two addresses, resource merge, user email change, deleted WP user, two simulated users in one process, foreign UUID access, and multisite switching.
-
-
SKILL.md 4.9 KB
--- name: fluentcart-customers-portal description: >- Implements and audits FluentCart customer identity, WP_User linkage, addresses, ownership checks, account creation, customer-scoped queries, and custom portal endpoints. Use when working with Customer, CustomerResource, getCurrentCustomer(), fct_customers, customer-profile REST routes, fluent_cart_api() customer-dashboard endpoint registration, customer merges or email changes, portal menus, order/subscription ownership, or long-running tests that switch users. metadata: wp-skills-author: "Soczó Kristóf" wp-skills-contact: "mailto:lonsdale201@hotmail.com" wp-skills-plugin: "fluent-cart" wp-skills-plugin-version-tested: "1.6.0" wp-skills-wp-version-tested: "7.0.2" wp-skills-php-min: "7.4" wp-skills-last-updated: "2026-08-06" --- # FluentCart customers and portal Treat Customer as the commerce identity and WP_User as an optional login identity. Enforce ownership for every customer-facing object. Read [customer-identity.md](references/customer-identity.md) before changing email/user links, moving resources, or exposing customer data. ## Resolve identity safely - Customer rows live in fct_customers and may link to WP users by user_id. - Orders, subscriptions, addresses, licenses, statistics, and labels reference Customer IDs, not WP user IDs. - CustomerResource::getCurrentCustomer() first resolves the logged-in WP user by user_id or matching email and may attach that user_id to the existing row. - Passing true may create a Customer from the current WP user. - Email matching is a linking operation. Validate verified ownership and duplicate/merge consequences before changing an address. Use Customers/CustomerResource and the normal resource-moving hooks for coordinated changes. Do not update order and subscription customer_id values piecemeal. ## Enforce object ownership CustomerFrontendPolicy only proves that a WP user is logged in. It does not authorize an arbitrary order, subscription, address, download, or license. For every custom handler: 1. resolve CustomerResource::getCurrentCustomer(); 2. query the requested object with customer_id equal to that customer; 3. reject absent/mismatched objects without revealing whether another customer owns them; 4. authorize the requested mutation separately; 5. use FluentCart lifecycle methods for the mutation. Never authorize solely by UUID, license key, address ID, download ID, or portal URL. ## Add a portal page Register at fluent_cart/init or later. Use a unique kebab-case slug that is not dashboard, purchase-history, subscriptions, licenses, downloads, or profile. ~~~php fluent_cart_api()->addCustomerDashboardEndpoint('acme-benefits', [ 'title' => __('Benefits', 'acme-addon'), 'render_callback' => static function (): void { $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer(); if (!$customer) { return; } echo '<div>' . esc_html__('Your benefits', 'acme-addon') . '</div>'; }, ]); ~~~ The render callback receives no arguments and echoes content inside the logged-in portal shell. Escape output and recheck any resource ownership inside it. A page_id can be supplied instead, but its content must still avoid exposing unscoped customer data. ## Keep caches request-scoped CustomerResource caches the current customer statically. Call resetCurrentCustomerRuntimeCache() between simulated users/requests in PHPUnit, WP-CLI loops, workers, or multisite switches. Do not let one user's cached Customer bleed into another iteration. ## Handle account and email changes - Do not create duplicate WP users merely because an order is paid; core may create/link an account according to store/order policy. - Normalize and validate email, then search both Customer and WP_User collision cases. - Move all connected resources through the normal move/change operation. - React to fluent_cart/customer_email_changed and fluent_cart/customer_resources_moved only after verifying their payload. - Recount customer statistics after repair/import, not on every read. - Never include PII in public cache keys, logs, HTML data attributes, or URLs. ## Cross-references - Use fluentcart-rest-headless for customer-facing endpoints. - Use fluentcart-orders-transactions for customer order queries. - Use fluentcart-licensing-pro for Pro license ownership. ## References - Official customer hooks: <https://dev.fluentcart.com/hooks/actions/customers-users/> - Verified Free source paths: - fluent-cart/app/Models/Customer.php - fluent-cart/api/Customers.php - fluent-cart/api/Resource/CustomerResource.php - fluent-cart/api/Resource/CustomerAddressResource.php - fluent-cart/app/Http/Policies/CustomerFrontendPolicy.php - fluent-cart/app/Http/Controllers/FrontendControllers/ - fluent-cart/api/FluentCartGeneralApi.php - fluent-cart/app/Hooks/Handlers/ShortCodes/CustomerProfileHandler.php - fluent-cart/app/Hooks/Handlers/UserHandler.php
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.