Files
formipay/RECOMMENDATION.md
dwindown 35569923a5 docs: add comprehensive audit report and architectural recommendation
Checkpoint before implementation. Includes audit findings (FINDINGS.md),
architectural recommendation (RECOMMENDATION.md), and existing code changes
to Form, Order, Render, and form-action.js from recent development.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 17:00:47 +07:00

18 KiB

🏗️ Formipay — Architectural Recommendation

Date: April 17, 2026
Context: Based on FINDINGS.md audit


The Question

Rebuild the plugin with React (admin + frontend), or keep the current SSR shortcode + Vue admin approach and fix all findings?


TL;DR — The Recommendation

Option B: Incremental Modernization (Keep SSR + Vue, Fix & Upgrade)

Do NOT do a full rewrite. Instead, adopt a phased modernization strategy that:

  1. Fixes all critical bugs immediately (Phase 1 — weeks 1-2)
  2. Introduces React only where it adds clear value (Phase 2 — weeks 3-6)
  3. Gradually migrates Vue admin → React as features are touched (Phase 3 — ongoing)

This is the industry-standard approach for WordPress plugins at this stage. Here's why and how.


Why NOT a Full React Rebuild

Factor Full React Rebuild Incremental Modernization
Time 3-4 months minimum 2-4 weeks for critical fixes
Risk Complete rewrite = complete regression risk Fixes are targeted and testable
Revenue No updates for 3-4 months Continuous delivery
WordPress ecosystem Fighting against WP conventions Working with WP conventions
SEO / Accessibility Client-side rendering = SEO problems for public forms SSR = perfect SEO & accessibility
Complexity Need build pipeline, state management, API layer Build on what works
Team onboarding Entire codebase unfamiliar Familiar patterns + gradual React intro

Industry Precedent

  • WooCommerce — Still PHP/SSR for storefront, React only for admin (block editor, settings). Did NOT rewrite frontend in React.
  • Easy Digital Downloads — Same approach. PHP templates + React for admin features.
  • GiveWP — PHP/SSR frontend, React for admin dashboard and form builder.
  • Gravity Forms — PHP/SSR rendering, React for the form builder only.

The pattern is clear: WordPress payment/e-commerce plugins keep SSR for public-facing forms (SEO, accessibility, speed, no-JS fallback) and use React for admin UX (form builder, dashboards, settings).


What to KEEP (SSR / PHP)

✅ Public form rendering (shortcode → PHP template)
✅ Order processing & payment flow (PHP backend)
✅ Email notifications (PHP wp_mail)
✅ Database layer (custom tables via dbDelta)
✅ Webhook handlers (REST routes in PHP)
✅ Thank-you page (PHP template)

Why: Server-side rendered forms are the correct choice for WordPress checkout forms. They provide:

  • Zero JavaScript dependency (forms work without JS)
  • Perfect SEO (crawlers see full HTML)
  • Fast initial paint (no JS bundle download needed)
  • Native WordPress shortcode/block integration
  • Accessibility out of the box (screen readers work)

What to MIGRATE to React

🔄 Admin form builder (currently partial Vue → full React)
🔄 Admin dashboard / analytics (new)
🔄 Admin order details view (currently Handlebars → React)
🔄 Admin settings pages (currently WPCFTO → custom React)
🔄 Gutenberg block (new — React is required)

What to ADD as React

🆕 React-powered shortcode replacement (as <script> islands)
🆕 React-based order tracking widget for customer portal
🆕 React-based product catalog (optional)

Implementation Plan

Phase 1: Critical Fixes & Stabilization (Weeks 1-2)

Goal: Make the existing plugin production-ready.

Week 1 — Critical Bug Fixes:
├── Fix Customer::update() undefined variables
├── Fix Order::delete() $id → $order_id
├── Fix Order::bulk_delete() loop variable
├── Fix Email::send_email() class reference
├── Fix Paypal::auto_cancel_order_on_timeout() import
├── Fix BankTransfer unique_code triple-call
├── Fix color field label ('Number' → 'Color')
└── Add missing nonce checks (Customer::tabledata)

Week 2 — Performance & Security:
├── Remove flush_rewrite_rules() from init → activation hook only
├── Replace maybe_serialize() in cookies with json_encode()
├── Add PayPal webhook signature verification
├── Add rate limiting on public AJAX endpoints
├── Cache currency/country JSON reads in static vars
├── Add pagination to Customer::tabledata
├── Optimize Order::tabledata queries (COUNT + GROUP BY)
└── Add uninstall.php for cleanup

Phase 2: React Admin Foundation (Weeks 3-6)

Goal: Set up React build pipeline and migrate the most impactful admin pages.

Week 3 — Build Pipeline:
├── Set up @wordpress/scripts (wp-scripts) build system
├── Configure webpack with React Fast Refresh
├── Create React component library structure
├── Set up API layer (fetch wrapper with nonce handling)
└── Create admin page shell component (sidebar + routing)

Week 4 — Form Builder (highest admin ROI):
├── Build drag-and-drop field palette (React)
├── Build field settings panel (React)
├── Build live preview canvas (React)
├── Connect to existing PHP save endpoints
└── Replace current Vue/Classic Editor metabox

Week 5 — Order Management & Dashboard:
├── Build order list page with filters (React + TanStack Table)
├── Build order detail view (replace Handlebars templates)
├── Build status change workflow with timeline
├── Build simple analytics dashboard (orders, revenue, charts)
└── Build notification log viewer

Week 6 — Settings & Product Editor:
├── Build global settings page (replace WPCFTO dependency)
├── Build product editor page (replace classic editor metaboxes)
├── Build coupon editor page
├── Build access items manager
└── Build license management page

Phase 3: Frontend Enhancements (Weeks 7-10)

Goal: Add React-powered frontend features while keeping SSR as the default.

Week 7-8 — React Island Architecture:
├── Create a render_php() method (existing SSR — default)
├── Create a render_react() method (new — optional)
├── Build React form renderer component
├── Implement "island hydration" — React attaches to SSR HTML
├── Add setting: "Render Mode: Classic (SSR) | Modern (React)"
└── Add multi-step form navigation (missing in current SSR)

Week 9 — Gutenberg Block:
├── Register formipay/form block with block.json
├── Block renders shortcode server-side in edit.js preview
├── Full React experience in editor
├── Settings panel in InspectorControls
└── Replace shortcode-only workflow

Week 10 — Customer Portal:
├── Build customer order history page (React)
├── Build order detail / download access page (React)
├── Build access link request form (React)
├── Integrate with WordPress user accounts
└── Shortcode [formipay_my_orders] for portal

Phase 4: Complete Missing Features (Weeks 11-16)

Week 11-12 — Payment & Commerce:
├── Implement ExchangeRateAPI (currently empty)
├── Implement License API endpoints (currently stubs)
├── Add Stripe payment gateway
├── Add tax calculation engine
└── Add product variations on frontend

Week 13-14 — Stock & Shipping:
├── Implement stock management (decrement, validation, messages)
├── Add weight-based shipping calculation
├── Add shipping zone support
└── Add order fulfillment workflow

Week 15-16 — Advanced Features:
├── Build donation form mode (pay-what-you-want, suggested amounts)
├── Add PDF invoice generation
├── Add CSV export for orders/customers
├── Add webhook notification system
└── Add analytics & reporting dashboard

Technical Architecture Diagram

┌─────────────────────────────────────────────────────────────┐
│                    WordPress Plugin                          │
│                                                             │
│  ┌──────────────────────┐    ┌──────────────────────────┐  │
│  │   PHP Backend Core   │    │   React Admin (wp-scripts)│  │
│  │                      │    │                          │  │
│  │  • Custom Post Types │◄──►│  • Form Builder          │  │
│  │  • Custom DB Tables  │    │  • Order Management      │  │
│  │  • Payment Gateways  │    │  • Product Editor        │  │
│  │  • Email System      │    │  • Settings Pages        │  │
│  │  • REST API Endpoints│    │  • Dashboard/Analytics   │  │
│  │  • Webhook Handlers  │    │  • License Manager       │  │
│  │  • Cron Jobs         │    │  • Notification Log      │  │
│  └──────────┬───────────┘    └──────────────────────────┘  │
│             │                                               │
│  ┌──────────▼───────────┐    ┌──────────────────────────┐  │
│  │  SSR Form Renderer   │    │  React Frontend Islands   │  │
│  │  (PHP + Shortcode)   │    │  (Optional Enhancement)   │  │
│  │                      │    │                          │  │
│  │  • [formipay] render │    │  • Multi-step navigation │  │
│  │  • Thank-you page    │    │  • Real-time validation  │  │
│  │  • Payment confirm   │    │  • Customer portal       │  │
│  │  • No-JS fallback ✅ │    │  • Gutenberg block       │  │
│  └──────────────────────┘    └──────────────────────────┘  │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Shared API Layer (WordPress REST API + admin-ajax)  │  │
│  │  • /wp-json/formipay/v1/*                            │  │
│  │  • admin-ajax.php actions (backward compat)          │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Build Toolchain

formipay/
├── package.json                    # @wordpress/scripts + deps
├── webpack.config.js               # Extend wp-scripts config
├── .eslintrc.js                    # ESLint with WP rules
├── .prettierrc                     # Prettier config
├── tsconfig.json                   # TypeScript (recommended)
│
├── src/                            # React source code
│   ├── admin/                      # Admin React app
│   │   ├── index.js                # Entry point
│   │   ├── components/             # Shared components
│   │   │   ├── DataTable/          # Reusable table component
│   │   │   ├── SettingsPanel/      # Settings form builder
│   │   │   ├── Modal/              # Confirmation modals
│   │   │   └── StatusBadge/        # Order status badges
│   │   ├── pages/                  # Page components
│   │   │   ├── Dashboard/
│   │   │   ├── FormBuilder/
│   │   │   ├── Orders/
│   │   │   ├── Products/
│   │   │   ├── Customers/
│   │   │   ├── Coupons/
│   │   │   ├── Licenses/
│   │   │   ├── AccessItems/
│   │   │   └── Settings/
│   │   ├── hooks/                  # Custom React hooks
│   │   │   ├── useOrders.js
│   │   │   ├── useProducts.js
│   │   │   └── useSettings.js
│   │   └── api/                    # API client
│   │       ├── client.js           # Fetch wrapper with nonce
│   │       ├── orders.js
│   │       ├── products.js
│   │       └── settings.js
│   │
│   ├── frontend/                   # Frontend React islands
│   │   ├── blocks/                 # Gutenberg blocks
│   │   │   └── formipay-form/
│   │   │       ├── block.json
│   │   │       ├── edit.jsx
│   │   │       └── view.js
│   │   └── widgets/               # Embeddable widgets
│   │       ├── CustomerPortal/
│   │       └── OrderTracker/
│   │
│   └── shared/                    # Shared between admin/frontend
│       ├── utils/
│       └── constants/
│
├── assets/build/                   # Compiled output (gitignored)
│   ├── admin.bundle.js
│   ├── admin.bundle.css
│   ├── frontend.bundle.js
│   └── blocks/
│
├── includes/                       # PHP backend (existing, fixed)
├── admin/                          # PHP admin pages (kept for PHP render)
├── public/                         # Public templates (SSR)
├── vendor/                         # Composer dependencies (new)
└── templates/                      # WordPress templates

package.json (minimal)

{
  "name": "formipay",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "wp-scripts build",
    "start": "wp-scripts start",
    "lint:js": "wp-scripts lint-js src/",
    "lint:css": "wp-scripts lint-style",
    "format": "wp-scripts format src/"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "@wordpress/api-fetch": "^6.0.0",
    "@wordpress/components": "^25.0.0",
    "@wordpress/data": "^9.0.0",
    "@wordpress/element": "^5.0.0",
    "@wordpress/i18n": "^4.0.0",
    "@tanstack/react-table": "^8.0.0",
    "@tanstack/react-query": "^5.0.0",
    "recharts": "^2.10.0"
  },
  "devDependencies": {
    "@wordpress/scripts": "^27.0.0"
  }
}

React Frontend Integration Strategy

Pattern: React Islands (Hybrid SSR + React)

The key pattern is "islands of interactivity" — the PHP renders the initial HTML (SSR), and React optionally hydrates specific sections:

// Render.php — The shortcode handler
public function shortcode($atts) {
    // ... existing SSR rendering (default) ...
    
    // Option: If React mode is enabled, render a mount point instead
    if ($render_mode === 'react') {
        return sprintf(
            '<div id="formipay-react-form-%d" data-form-id="%d" data-nonce="%s"></div>',
            $post_id,
            $post_id,
            wp_create_nonce('formipay_order_submit')
        );
    }
    
    // Default: existing PHP-rendered form (SSR)
    return $this->render_php_form($post_id);
}
// src/frontend/widgets/FormRenderer.jsx
import { createRoot } from '@wordpress/element';
import Form from './components/Form';

document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('[id^="formipay-react-form-"]').forEach(el => {
        const formId = parseInt(el.dataset.formId);
        const nonce = el.dataset.nonce;
        createRoot(el).render(<Form formId={formId} nonce={nonce} />);
    });
});

This gives you:

  • Backward compatibility — existing [formipay] shortcodes keep working with SSR
  • Opt-in React — new features use React when enabled
  • Gutenberg block — renders React in editor, SSR or React on frontend
  • No lock-in — each form can use either mode independently

What to Do With the Vue Code

The existing Vue admin code (admin/assets/vue/) should be deprecated, not deleted immediately:

  1. Keep Vue code working during Phase 1 (critical fixes)
  2. Build React equivalents in Phase 2 alongside Vue
  3. Switch admin pages to React one at a time (form builder first)
  4. Remove Vue code once all pages are migrated in Phase 3

Do NOT try to mix Vue and React on the same page — they'll conflict. Migrate page-by-page.


Gutenberg Block Strategy

// src/frontend/blocks/formipay-form/block.json
{
    "apiVersion": 3,
    "name": "formipay/form",
    "title": "Formipay Form",
    "category": "widgets",
    "attributes": {
        "formId": { "type": "number", "default": 0 },
        "renderMode": { "type": "string", "default": "ssr" }
    },
    "supports": {
        "html": false,
        "align": true
    },
    "editorScript": "file:./index.js",
    "editorStyle": "file:./index.css",
    "style": "file:./style-index.css",
    "render": "file:./render.php"
}

The render.php file uses Server-Side Rendering (required by WordPress for blocks), which calls the existing PHP form renderer. The editor experience (edit.jsx) is fully React.


Testing Strategy

Phase 1: Manual testing of all bug fixes
Phase 2: PHPUnit for backend + Jest for React components
Phase 3: E2E tests with Playwright for critical flows
Phase 4: CI/CD with GitHub Actions

Coverage targets:
├── Backend (PHP): 80% on payment + order paths
├── Admin (React): 70% on components
├── Frontend: E2E for form submission, payment, thank-you
└── API: Integration tests for all REST endpoints

Cost-Benefit Summary

Approach Time Cost Risk Deliverable
Full React rebuild 3-4 months Very high Very high Complete rewrite, likely with new bugs
Keep PHP + fix only 2-3 weeks Low Low Fixes bugs, no UX improvement
Incremental modernization 4-6 weeks for Phases 1-2 Medium Low-Medium Bugs fixed + modern admin UX

This approach:

  1. Delivers value immediately — critical bugs fixed in Week 1
  2. Reduces risk — changes are targeted and testable
  3. Follows WordPress conventions — SSR forms, React admin
  4. Enables future growth — React foundation for new features
  5. Matches industry patterns — same approach as WooCommerce, GiveWP, Gravity Forms

End of recommendation.