1. Directory Structure & PSR-4 Namespace Decoupling
Monolithic class patterns are strictly prohibited in Recurify. Every component follows Single Responsibility principles, mapped cleanly to the `CodersGrow\Recurify` root namespace via PSR-4:
src/
├── Admin/ # Presentation controllers, SettingsManager, view renderers
├── API/ # WP_REST_Controller implementations (Subscriptions, Plans, KPIs)
├── Bootstrap/ # Activator (migrations), Deactivator, Uninstaller, Autoloader, Plugin singleton
├── Common/ # Constants, capabilities, and reusable traits
├── Database/
│ ├── Migrations/ # Versioned dbDelta table migrations
│ └── Repositories/ # Prepared SQL repositories (Subscription, Plan, Log, Survey)
├── Exceptions/ # Custom domain & infrastructure exceptions
├── Frontend/ # Cart/checkout, customer account, and plan switcher controllers
├── Hooks/ # Isolated action/filter registrars (Admin, Frontend, Global, Woo)
├── Integrations/ # AbstractGatewayAdapter, Stripe, PayPal, ContentGateService
├── Models/ # Domain entities (Subscription, SubscriptionItem, SubscriptionPlan)
├── Services/ # Pure business logic (Lifecycle, Renewal, Dunning, Proration, Box, Gift)
└── Utils/ # Logger (wc_get_logger facade), Sanitizer, View renderer, DateHelper
The Hook-Registrar Pattern
To preserve testability and prevent bootstrap bloat, hook classes (`AdminHooks`, `FrontendHooks`, `GlobalHooks`, `WooCommerceHooks`) contain zero business logic. They simply instantiate the required controller or service and bind methods to WordPress actions and filters.
—
2. Dedicated Relational Schema vs. Legacy `wp_posts` Bloat
Rather than storing subscriptions as custom post types, Recurify provisions 5 dedicated relational SQL tables using versioned `dbDelta()` migrations during plugin activation:
┌────────────────────────────────────────────────────────────────────────┐
│ Recurify Custom Relational Database Schema │
├────────────────────────────────────────────────────────────────────────┤
│ wp_cg_recurify_subscriptions (Aggregate Root & Status State) │
│ wp_cg_recurify_subscription_items (Line items, quantities, box slots)│
│ wp_cg_recurify_plans (Recurring tiers, trial intervals) │
│ wp_cg_recurify_logs (Immutable event audit trail) │
│ wp_cg_recurify_cancellation_surveys(Exit metrics & deflection results)│
└────────────────────────────────────────────────────────────────────────┘
Key Architectural Advantages:
1. Targeted Relational Indexing: Fields frequently queried during billing cycles—such as `status`, `next_payment_date`, and `customer_id`—possess dedicated B-tree indexes. Looking up 500 subscriptions due for renewal executes in sub-milliseconds without table scanning.
2. Zero `wp_postmeta` Overhead: Standard WooCommerce checkout performance remains pristine, as subscription mutations never lock core catalog tables.
3. Clean Uninstallation: When `uninstall_erase_data` is configured in `cg_recurify_settings`, `uninstall.php` safely drops all 5 tables and purges scheduled actions, leaving zero residual database clutter.
—
3. Concurrency Protection & Action Scheduler Mutex Locks
In recurring commerce, the double-billing race condition is a catastrophic failure mode. It occurs when two asynchronous workers (e.g., overlapping cron runners or simultaneous webhooks) attempt to process renewal billing for the same subscriber at the exact same moment.
Recurify resolves this using a multi-layer concurrency control workflow in `RenewalService`:
Action Scheduler Hook: codersgrow_recurify_scheduled_renewal
│
▼
[ Acquire Transient Mutex Lock ]
Key: recurify_lock_renewal_{$sub_id}
TTL: 300 to 600 seconds
│
┌─────────────┴─────────────┐
▼ ▼
Lock Acquired? Lock Held?
│ │
│ ▼
│ Abort Immediately!
│ Log audit warning.
▼
[ Pre-Flight Inventory Stock Check ]
│
▼
[ Create HPOS Renewal WC_Order ]
│
▼
[ Process Gateway Recurring Payment ]
│
▼
[ Release Mutex via wp_delete_transient() ]
Action Scheduler Execution
Renewals are dispatched asynchronously via Action Scheduler, the high-throughput background processing library maintained by WooCommerce. If Action Scheduler is absent, the engine falls back to `wp_schedule_single_event()`.
—
4. The 11-Stage Finite Lifecycle State Machine
Subscription states in Recurify are strictly deterministic. Transitions are governed by `LifecycleService::transition_to()`, preventing invalid status changes (such as jumping from `cancelled` directly back to `trial`).
┌───────────────┐
│ Pending │
└───┬───────┬───┘
Trial Cart │ │ Regular Checkout
┌─────────────────┘ └─────────────────┐
▼ ▼
┌───────────┐ 1st Payment Success ┌───────────┐
│ Trial ├──────────────────────────────►│ Active │◄─────────────────┐
└─────┬─────┘ └───┬───┬───┘ │
│ Cancelled │ │ │
▼ │ │ Renewal Failed │
┌───────────┐ Customer Pauses │ ▼ │
│ Cancelled │◄──────────────────────────────────┼─►┌─────────┐ │
└───────────┘ │ │ On-Hold │ │
▲ │ └───┬─────┘ │
│ Grace Expired │ │ Dunning Success │
┌─────┴─────┐ Max Retries Exceeded │ └───────────────────┘
│ Suspended │◄──────────────────────────────────┘
└───────────┘
The 11 discrete lifecycle states comprise: `pending`, `trial`, `active`, `on-hold`, `paused`, `pending-cancel`, `cancelled`, `expired`, `suspended`, `failed`, and `completed`. Terminal states (`cancelled`, `completed`, `expired`) are immutable, protecting stores from billing orphaned accounts.
—
5. Mathematical Proration Engine
When a customer upgrades or downgrades their plan mid-cycle, `ProrationService` calculates adjustments down to the exact second. The engine eliminates fractional day rounding errors across leap years and variable month lengths:
// Mathematical calculation of unused cycle credit
$seconds_remaining = max(0, $cycle_end_timestamp - $current_timestamp);
$total_cycle_seconds = $cycle_end_timestamp - $cycle_start_timestamp;
$unused_credit = $current_plan_price * ($seconds_remaining / $total_cycle_seconds);
$prorated_charge = max(0.00, $new_plan_price – $unused_credit);
Three Proration Strategies:
1. `charge_immediately`: Invoices and captures the prorated difference immediately via checkout, advancing the billing cycle to today.
2. `credit_next_cycle`: Retains existing dates and applies credit or debit adjustments to the upcoming scheduled renewal invoice.
3. `no_proration`: Leaves current balances untouched and charges the new plan price on the next scheduled renewal.
—
6. The 6 Pillars of Recurify Security
Security is embedded into every architectural layer of Recurify, enforcing the 6 Pillars of WordPress Security:
| Security Pillar | Technical Implementation in Recurify |
|---|---|
| 1. Nonce & CSRF Defense | Admin actions use `check_admin_referer()`; customer portal actions use `wp_verify_nonce($_REQUEST[‘security’], ‘cg_recurify_frontend_nonce’)`. |
| 2. Role Authorization | Admin capabilities require `manage_woocommerce`, filterable via `apply_filters(‘cg_recurify_admin_capability’, …)`. Settings update permissions are bound to `option_page_capability_cg_recurify_settings_group`. |
| 3. IDOR Defense | Every mutation in `CustomerAccountController` and `RESTSubscriptionController` checks: `$subscription->get_customer_id() === get_current_user_id()`. Customer REST queries inject `WHERE customer_id = %d`. |
| 4. SQL Injection Prevention | All database operations in Repositories use `$wpdb->prepare()`. Sorting parameters (`ASC`/`DESC`) and column keys are strictly validated against whitelists. |
| 5. Input / Output Hygiene | Inputs pass through `sanitize_text_field()`, `absint()`, or `floatval()`. Template outputs use `esc_html()`, `esc_attr()`, `esc_url()`, and `wp_json_encode()`. |
| 6. Cryptographic Vaulting | Zero raw credit card numbers (PAN) or CVVs are stored in SQL. Gift tokens are generated with `bin2hex(random_bytes(32))`, and token validation uses timing-safe `hash_equals()`. |
—
7. REST API v1 (`codersgrow/recurify/v1`)
For headless web applications, native mobile apps, and external ERP systems, Recurify exposes an API following WordPress `WP_REST_Controller` conventions:
GET /wp-json/codersgrow/recurify/v1/subscriptions
GET /wp-json/codersgrow/recurify/v1/subscriptions/{id}
POST /wp-json/codersgrow/recurify/v1/subscriptions
POST /wp-json/codersgrow/recurify/v1/subscriptions/{id}/switch-plan
POST /wp-json/codersgrow/recurify/v1/subscriptions/{id}/pause
POST /wp-json/codersgrow/recurify/v1/subscriptions/{id}/resume
POST /wp-json/codersgrow/recurify/v1/subscriptions/{id}/cancel
GET /wp-json/codersgrow/recurify/v1/analytics
Each endpoint enforces typed JSON schemas, sanitized request parameters, and granular permission callbacks.
—
8. Automated Testing & Verification Framework
Recurify includes a zero-dependency CLI test runner (`run-tests.php`) alongside standard `phpunit.xml.dist` integration, enabling sub-second unit and integration testing without requiring external Docker services:
# Execute the complete Recurify test runner
php run-tests.php
Test Coverage Benchmark:
- Total Test Suites: 21 test suites
- Total Executed Tests: 244+ automated tests
- Total Assertions: 1,650+ strict assertions
- Pass Rate: 100% Pass Rate (0 Failures, 0 Errors)
The test suites validate every architectural layer: HPOS order generation, concurrency mutex release, dunning schedules, proration math, membership shortcode rules, and REST API permission callbacks.
—
Conclusion & Developer Resources
By decoupling subscription state into dedicated relational tables, leveraging Action Scheduler with mutex concurrency locks, and enforcing strict OOP design patterns, Recurify establishes a modern standard for woocommerce recurring billing.
- 📚 Browse the Recurify Developer Documentation
- 🧪 Run the Test Suite in the Interactive Sandbox