WP Manifestindependent plugin directory
manifest / ecommerce / wp-power-partner

Power Partner | 讓每個人都可以輕鬆地販售網站模板 WP Registry grade F. Critical findings · 4 findingsWP RegistryCritical findings · 4 findingsOpen the reportF #9 in eCommerce

Power Partner 是一個 WordPress 套件,安裝後,可以讓你的 Woocommerce 商品與 cloud.luke.cafe 的模板網站連結,並且可以讓使用者自訂商品的價格,當用戶在您的網站下單後,會自動在 cloud.luke.cafe 創建網站,並且自動發送通知給用戶跟您。

★ 4stars
8.4krelease downloads
≈130active sites
v3.5.4latest · 1mo ago

Install

Download power-partner.zip From the v3.5.4 release on GitHub, Sep 2, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/zenbuapps/wp-power-partner/releases/download/v3.5.4/power-partner.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Version: 3.2.4 | Requires: PHP 8.1+, WP 5.7+, WooCommerce 7.6+, Woo Subscriptions 5.9+, Powerhouse 3.3.23+

Power Partner is a WordPress plugin that lets you sell WordPress website templates as WooCommerce subscription products. After installation and connecting your cloud.luke.cafe partner account, your WooCommerce subscription products can be linked to template sites. When a customer completes their first subscription payment, a WordPress site is automatically provisioned from the template and the credentials are emailed directly to the customer.


Features

  • Automatic Site Provisioning — On subscription creation, clones a template site for the customer (supports both WPCD legacy and PowerCloud new architecture)
  • Dual Hosting Backend — powercloud (default, via api.wpsite.pro) and wpcd (legacy, via cloud.luke.cafe) can be selected per product
  • Subscription Lifecycle Automation — Automatically suspends provisioned sites when a subscription fails; re-enables when it recovers
  • Configurable Email Templates — Rich HTML email system with ##TOKEN## substitution (customer name, site URL, credentials, etc.) triggered at configurable lifecycle points
  • License Code Management — Integrates with cloud.luke.cafe to create, expire, and recover plugin license codes tied to subscriptions
  • Admin Dashboard — React SPA (Ant Design) with tabs for: site list, email config, manual provisioning, settings, license codes, PowerCloud auth
  • Customer Site List — Frontend shortcode [power_partner_current_user_site_list] shows a customer's provisioned sites
  • WooCommerce Integration — Custom columns on orders and subscriptions, meta boxes, and product data tabs for configuration

Requirements

Dependency Minimum Version
PHP 8.1
WordPress 5.7
WooCommerce 7.6.0
Woo Subscriptions 5.9.0
Powerhouse (zenbuapps) 3.3.23

Installation

  1. Install and activate the required plugins (WooCommerce, Woo Subscriptions, Powerhouse).
  2. Upload or install the power-partner plugin and activate it.
  3. Go to Powerhouse → Power Partner in wp-admin.
  4. Log in with your cloud.luke.cafe partner credentials to connect your account.
  5. Go to a subscription product, open the General tab, and configure:
    • Host Type — powercloud (new) or wpcd (legacy)
    • Host Region — JP / TW / US West / etc.
    • Template Site — select from your allowed templates
    • Plan — (PowerCloud only) select an infrastructure plan
  6. Set up email templates under the Email 設定 tab.
  7. Configure Settings (e.g. how many days before suspending a site after subscription failure).

Development Setup

Requirements

  • Node.js + pnpm
  • PHP 8.1+ with Composer
  • A local WordPress install with WooCommerce and Woo Subscriptions

Install Dependencies

# PHP dependencies
composer install

# Install JS dependencies
pnpm install

Development

# Start Vite dev server (port 5176)
pnpm dev

# Production build → js/dist/
pnpm build

# WP-specific production build
pnpm build:wp

Code Quality

# PHP lint
composer lint          # phpcs

# JS lint
pnpm lint

# Format JS
pnpm format

Release

pnpm sync:version      # Sync version: package.json → plugin.php
pnpm release:patch     # Bump patch, build, create GitHub release
pnpm release:minor     # Bump minor version
pnpm release:major     # Bump major version
pnpm zip               # Create distributable ZIP

Architecture Overview

plugin.php                      # Plugin class, required plugins, activate hook
inc/classes/
├── Bootstrap.php               # Singleton orchestrator — wires all classes
├── Order.php                   # Admin order columns + metabox (site-sync status)
├── ShopSubscription.php        # Subscription meta helpers (linked_site_ids)
├── Shortcode.php               # [power_partner_current_user_site_list]
├── Admin/Menu/Setting.php      # Admin page HTML mount point
├── Api/
│   ├── Main.php                # Core REST endpoints
│   ├── Connect.php             # Partner ID / account-info endpoints
│   ├── Fetch.php               # Abstract: WPCD API (legacy)
│   ├── FetchPowerCloud.php     # Abstract: PowerCloud API (new)
│   └── User.php                # Customer search endpoints
├── Domains/
│   ├── Email/                  # Email DTO, scheduling, hooks
│   ├── Site/                   # Site disable/enable scheduling
│   ├── LC/                     # License code lifecycle
│   └── Settings/               # Watches settings changes, reschedules
├── Product/
│   ├── SiteSync.php            # Fires on INITIAL_PAYMENT_COMPLETE
│   └── DataTabs/
│       ├── LinkedSites.php     # Product fields: host, template, plan
│       └── LinkedLC.php        # Product fields: linked license products
└── Utils/
    ├── Base.php                # Env-based API auth, constants
    └── Token.php               # ##TOKEN## replacement

js/src/
├── main.tsx                    # Mounts App1 + App2
├── App1.tsx                    # Admin app (Ant Design)
├── App2.tsx                    # Frontend app (Shadow DOM)
└── pages/
    ├── AdminApp/               # Dashboard + Login
    └── UserApp/                # Customer site list

Subscription Lifecycle

Customer purchases subscription
        ↓
INITIAL_PAYMENT_COMPLETE
        ↓
SiteSync::site_sync_by_subscription()
        ↓
[PowerCloud] FetchPowerCloud::site_sync()   OR   [WPCD] Fetch::site_sync()
        ↓
Site provisioned → pp_site_sync_by_subscription fired (public extension point)
        ↓
[PowerCloud] email_payloads_tmp stored → sent 4 min later via ActionScheduler
[WPCD]       CloudServer calls back /customer-notification → sent immediately

--- Later ---

Subscription FAILS (active → cancelled/on-hold)
        ↓
DisableHooks: schedule site disable in N days
LC\LifeCycle: schedule license code expiry in 4h

Subscription RECOVERS (failed → active)
        ↓
DisableHooks: cancel disable schedule, re-enable all sites
LC\LifeCycle: cancel expiry, recover license codes via API

Key Concepts

Subscription "Failure" Definition

Active → Cancelled / On-Hold / Pending-Cancel = FAILED
Active → Expired = NOT failed (natural end of subscription)

Use the Action enum from Powerhouse for all subscription lifecycle hooks:

use J7\Powerhouse\Domains\Subscription\Shared\Enums\Action;
\add_action(Action::SUBSCRIPTION_FAILED->get_action_hook(), [$this, 'my_callback'], 10, 2);

Linked Site IDs

Each subscription stores the IDs of its provisioned sites as multi-value post meta under the key pp_linked_site_ids. Always use helper methods:

ShopSubscription::get_linked_site_ids($subscription_id);   // read
ShopSubscription::update_linked_site_ids($id, $ids);       // write
ShopSubscription::change_linked_site_ids($id, $ids);       // atomic move

Email Tokens

Email subject and body support ##TOKEN## substitution:

  • ##FIRST_NAME##, ##ADMINURL##, ##SITEPASSWORD##, ##IPV4##, ##ORDER_ID##, etc.
  • See Utils\Token::replace() and Utils\Token::get_order_tokens() for the full list.

Shortcode

[power_partner_current_user_site_list]

Renders a React app (App2) showing the current logged-in customer's provisioned sites and license codes. Uses Shadow DOM for style isolation.


REST API (Summary)

Namespace: /wp-json/power-partner/

Route Method Auth Purpose
/customer-notification POST IP whitelist Email customer site credentials (WPCD callback)
/link-site POST IP whitelist Link site ID to subscription
/settings POST admin Save plugin settings
/emails GET/POST admin Get/save email templates
/manual-site-sync POST admin Manually provision a site
/partner-id GET/POST/DELETE public/admin Manage partner connection
/subscriptions GET admin List user subscriptions
/change-subscription POST admin Reassign site to subscription
/powercloud-api-key POST admin Store PowerCloud API key
/apps GET public Get subscription IDs for site IDs

WordPress Options

Option Description
power_partner_settings {power_partner_disable_site_after_n_days, emails[]}
power_partner_partner_id cloud.luke.cafe partner ID
power_partner_account_info Encrypted account data

WordPress Transients

Transient TTL Description
power_partner_allowed_template_options 7 days WPCD template list
power_partner_allowed_template_options_powercloud 7 days PowerCloud template list
power_partner_open_site_plan_options_powercloud 7 days PowerCloud plan list
power_partner_powercloud_api_key_{user_id} 30 days PowerCloud API key per user

Subscription Status Logic

Status Meaning
wc-active 已啟用 — site is live
wc-cancelled 已取消 — triggers site disable schedule
wc-on-hold 保留 — triggers site disable schedule
wc-pending-cancel 待取消 — triggers site disable schedule
wc-expired 已過期 — natural end, does NOT trigger site disable

Changelog

Unreleased

fix(email): 修正「客戶續訂成功後」信從未寄出的問題(issue #16)

問題根源:subscription_success 信原本綁定 Powerhouse SUBSCRIPTION_SUCCESS hook,但 stock WCS 不允許 cancelled/expired → active 的狀態轉換,導致此 hook 在正常流程中從未觸發,此信件型別自上線以來實際上從未寄出。

行為變更:

  • 訂閱從 on-hold(待處理)/ pending-cancel(待取消)等狀態恢復為 active 時觸發成功信排程,每次成功續訂都會寄一封(符合 UI 標籤「續訂成功後 N 天發送」語意)
  • pending → active(首次付款啟用)不觸發成功信,避免與開站信重疊
  • 防線機制:最少延遲 10 分鐘 + 寄送當下狀態複查(須仍為 active)+ 離開 active 時自動取消未寄成功信
  • 反向對稱取消:催繳信與成功信互相取消,防止 WCS 自動續訂 active → on-hold → active 震盪期間誤寄或兩封同時寄出

站長注意:已勾選「客戶續訂成功後」Email 模板的站長,此為行為改變(過去靜默無作用,現在會實際寄出)。如不需要可至 Powerhouse → Power Partner → Email 設定 停用該模板。


Contributing

All PHP must pass PHPCS (WPCS rules) and PHPStan (configured in phpstan.neon).
All JS/TS must pass ESLint before committing.

Releases

Newest 25 of 60 recorded releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v3.5.4 latest
Sep 2, 2026 1mo ago
power-partner.zip
797
Sep 1, 2026 1mo ago
power-partner.zip
133
Aug 29, 2026 1mo ago
power-partner.zip
407
Aug 25, 2026 1mo ago
power-partner.zip
93
Aug 24, 2026 2mo ago
power-partner.zip
78
Jul 7, 2026 3mo ago
power-partner.zip
137
Jun 22, 2026 4mo ago
power-partner.zip
180
Jun 22, 2026 4mo ago
power-partner.zip
15
Jun 22, 2026 4mo ago
power-partner.zip
26
Jun 15, 2026 4mo ago
power-partner.zip
125
Jun 12, 2026 4mo ago
power-partner.zip
165
Jun 11, 2026 4mo ago
power-partner.zip
104
Jun 10, 2026 4mo ago
power-partner.zip
104
May 14, 2026 5mo ago
power-partner.zip
147
May 13, 2026 5mo ago
power-partner.zip
141
May 13, 2026 5mo ago
power-partner.zip
22
May 5, 2026 5mo ago
power-partner.zip
129
Apr 24, 2026 6mo ago
power-partner.zip
151
Apr 16, 2026 6mo ago
power-partner.zip
158
Apr 9, 2026 6mo ago
power-partner.zip
162
Apr 7, 2026 6mo ago
power-partner.zip
139
Apr 4, 2026 6mo ago
power-partner.zip
131
Mar 23, 2026 7mo ago
power-partner.zip
17
Mar 19, 2026 7mo ago
power-partner.zip
1
Mar 18, 2026 7mo ago
power-partner.zip
1
All releases on GitHub →

Active-site estimate ≈130 comes from the median of recent superseded releases. Method.

F grade

Security

Power Partner | 讓每個人都可以輕鬆地販售網站模板 3.5.4 · audited by WP Registry

Critical findings.

1 critical 2 high 1 medium
Audited release
3.5.4
Findings
4
Worst severity
critical
Content hash
dc6fb2e133e98bdbe639fec8…

Findings

  • critical Hardcoded CloudServer application passwords localized to every frontend page

    hardcoded_credentials

    Utils\Base::set_api_auth() embeds WordPress application passwords for the shared user powerpartner and assigns them on the Bootstrap singleton (username, psw, t=base64(username:password)). Production is password 'uJsk Gu3S pwUG r6ia P9zy Xjrj' against https://cloud.luke.cafe; staging ('BU6g 9DOh G6xw gXQo gi6u hRGw' @ https://test1.powerhouse.cloud) and local ('7t4T WpSr HgZL Auyl TtOw USyG' @ http://cloud.local) are in the same switch. PHP then uses Authorization: Basic on site-sync, disable-site, enable-site, template-sites, license-codes/products, and daily billing. Independently, Bootstrap::frontend_enqueue_script() is hooked to wp_enqueue_scripts with no page/shortcode/capability gate, so wp_localize_script prints env.t (and cloudBaseUrl) into power_partner_data on every public HTML response, including for unauthenticated visitors. The compiled SPA reads env.t and sends Authorization: Basic ${t} to ${cloudBaseUrl}/wp-json/power-partner-server (e.g. POST /logs/{id}/detail). Because the zip is public on GitHub, every install shares one vendor WP user; viewing any site that runs this plugin (or the plugin source) is enough to impersonate that user and provision, suspend, or enumerate sites on the vendor platform.

    inc/classes/Utils/Base.php:29-65

    Recommendation

    Revoke and rotate every leaked application password on cloud.luke.cafe, test1.powerhouse.cloud, and cloud.local immediately; treat git history as compromised. Stop shipping shared vendor credentials in plugin source or in wp_localize_script. Authenticate CloudServer calls with a per-site token issued at partner connect, keep it in a non-autoloaded option, and never print it to the front end. Load the admin SPA only on the Power Partner admin page.

  • high Unauthenticated GET /customers leaks login names and emails for arbitrary user IDs

    information_disclosure

    GET /wp-json/power-partner/customers is registered with permission_callback __return_true. get_customers_callback() reads query param user_ids and passes it to get_users(['include' => $user_ids]) with no capability, no ownership check, and no field allowlist beyond the response mapper. The JSON returns id, user_login, user_email, and display_name. An unauthenticated visitor can enumerate sequential IDs (user 1 is typically the administrator) and harvest store customer and admin email addresses. A sibling route customers-by-search correctly requires manage_options, which shows this was not an intentional public directory.

    inc/classes/Api/User.php:43-51,133-166

    Recommendation

    Require manage_options (or list_users) on GET /customers, or restrict include[] to the current user's ID for non-admins. Do not return user_email/user_login to unauthenticated callers. Prefer the already-gated customers-by-search route and remove this endpoint.

  • high Site-provisioning webhooks authenticated only by REMOTE_ADDR (RFC1918 and staging always allowed)

    missing_auth

    POST /wp-json/power-partner/customer-notification and POST /link-site use permission_callback check_ip_permission() with no nonce, no shared secret, and no capability. That helper returns true when wp_get_environment_type() is local or staging (entirely open), when REMOTE_ADDR is one of four hardcoded vendor IPs, when REMOTE_ADDR is anywhere in 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16, or when it falls in 61.220.44.0-10. Sites behind nginx/Docker/load balancers often see a private REMOTE_ADDR, so the webhook becomes unauthenticated on those installs. post_customer_notification_callback() looks up an arbitrary CUSTOMER_ID, optionally binds NEW_SITE_ID onto a subscription (pp_linked_site_ids / pp_site_url — the same meta DisableHooks later uses to stop/start sites), and EmailService::send_mail() sends the configured HTML site_sync template with attacker-controlled FRONTURL/ADMINURL/SITEUSERNAME/SITEPASSWORD to that customer's email. post_link_site_callback() appends an arbitrary site_id onto any subscription_id. Observable: phishing mail that looks like the store's official 'your site is ready' message, and rebinding so the wrong site is suspended when a subscription fails.

    inc/classes/Api/Main.php:38-56,208-335,364-377,889-920

    Recommendation

    Replace IP allowlists with a per-site HMAC or bearer secret stored at partner connect and sent by CloudServer. Deny by default; never treat local/staging or RFC1918 as authenticated. Bind site IDs only for subscriptions that belong to the claimed customer and whose parent order is awaiting that callback. Do not accept attacker-supplied SITEPASSWORD into wp_mail without verifying the callback against a pending provision job.

  • medium Additional unauthenticated REST routes leak partner ID, subscription IDs, and next-payment times

    missing_permission_callback

    Several other routes are registered with permission_callback __return_true. GET /power-partner/partner-id returns option power_partner_partner_id (the CloudServer user id used on every vendor API call). GET /power-partner/apps?app_ids[]= maps each site/app id to shop_subscription post IDs via get_posts(post_status=any, meta_key=pp_linked_site_ids). GET /power-partner/subscriptions/next-payment?ids[]= (Domains\LC\Core\Api) returns each subscription's next_payment timestamp with no ownership check. Together with the leaked Basic token and partner_id this is enough to target the vendor API and to map which WooCommerce subscriptions back which provisioned sites. None of these are needed as public unauthenticated APIs.

    inc/classes/Api/Main.php:154-162

    Recommendation

    Default these GET routes to manage_options. If the customer site list needs next-payment or linked site IDs, add a current_user_can/ownership check and return only the caller's subscriptions. partner-id should not be public; the server already has it in options for outbound calls.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.