diff --git a/SETUP_WIZARD_DESIGN.md b/SETUP_WIZARD_DESIGN.md index 21471e8..3e432a8 100644 --- a/SETUP_WIZARD_DESIGN.md +++ b/SETUP_WIZARD_DESIGN.md @@ -1,13 +1,403 @@ -# Setup Wizard Design Document +# Settings & Setup Wizard - Implementation Guide + +> **Living Document** - Track progress, maintain standards, keep on track ## Overview -A guided onboarding experience for new WooNooW stores, helping merchants configure essential settings in 5-10 minutes. +WooNooW Settings act as a **"better wardrobe"** for WooCommerce configuration - reading WC's bone structure, transforming complexity into simplicity, and enhancing performance. + +## Core Philosophy +1. **Read WooCommerce Structure** - Listen to WC's registered entities (gateways, shipping, settings) +2. **Transform & Simplify** - Convert complex WC settings into clean, categorized UI +3. **Enhance Performance** - Direct DB operations where safe (30s → 1-2s) +4. **Respect Ecosystem** - Auto-support WC-compliant addons +5. **No New Hooks** - Listen to WC hooks, don't create parallel system ## Goals -1. **Fast Setup** - Get store operational in < 10 minutes +1. **Fast Setup** - Get store operational in < 10 minutes (wizard) 2. **Smart Defaults** - Pre-configure based on location/industry 3. **Progressive** - Can skip and complete later 4. **Educational** - Teach key concepts without overwhelming +5. **Performance** - Settings save in 1-2s (not 30s like WC) + +--- + +# IMPLEMENTATION CHECKLIST + +## Phase 1: Payment Gateways Foundation ⏳ + +### Backend - Payment Gateways Provider +- [ ] Create `includes/Compat/PaymentGatewaysProvider.php` + - [ ] `get_gateways()` - Read from `WC()->payment_gateways()->payment_gateways()` + - [ ] `categorize_gateway()` - Classify as manual/provider/other + - [ ] `transform_gateway()` - Convert WC format to clean JSON + - [ ] `get_gateway_settings()` - Read gateway form fields + - [ ] `categorize_settings()` - Group into basic/api/advanced + - [ ] `check_requirements()` - Validate PHP extensions, dependencies + - [ ] `get_webhook_url()` - Generate webhook URLs for gateways + - [ ] Handle gateways that don't extend `WC_Payment_Gateway` gracefully + +### Backend - REST API Controller +- [ ] Create `includes/API/PaymentsController.php` + - [ ] `GET /woonoow/v1/payments/gateways` - List all gateways + - [ ] `GET /woonoow/v1/payments/gateways/{id}` - Get single gateway + - [ ] `POST /woonoow/v1/payments/gateways/{id}` - Save gateway settings + - [ ] `POST /woonoow/v1/payments/gateways/{id}/toggle` - Enable/disable + - [ ] Permission checks (manage_woocommerce) + - [ ] Error handling with proper HTTP codes + - [ ] Validation using gateway's own validation methods + - [ ] Response caching headers + +### Frontend - Generic Form Builder +- [ ] Create `src/components/settings/GenericGatewayForm.tsx` + - [ ] Support field types: text, password, checkbox, select, textarea + - [ ] Support field types: number, email, url + - [ ] Unsupported field types → Show link to WC settings + - [ ] Field validation (required, pattern, min, max) + - [ ] Field descriptions and help text + - [ ] Conditional field visibility + - [ ] Multi-page form for 20+ fields (tabs: Basic, API, Advanced) + - [ ] Loading states + - [ ] Error states with helpful messages + - [ ] Success feedback + +### Frontend - Update Payments Page +- [ ] Update `src/routes/Settings/Payments.tsx` + - [ ] Replace mock data with API calls + - [ ] Use `useQuery` for fetching gateways + - [ ] Use `useMutation` for saving settings + - [ ] Optimistic updates for enable/disable toggles + - [ ] Refetch on window focus + - [ ] Manual refresh button + - [ ] Loading skeleton while fetching + - [ ] Empty state (no gateways) + - [ ] Error boundary for gateway cards + - [ ] Group by category (manual, providers, other) + +### UI Components +- [ ] Create `src/components/settings/GatewayCard.tsx` + - [ ] Show gateway icon, name, description + - [ ] Show enabled/disabled badge + - [ ] Show connected/not connected badge (for providers) + - [ ] Show requirements warning if not met + - [ ] "Manage" button → Opens settings modal/page + - [ ] "Enable/Disable" toggle + - [ ] "View in WooCommerce" link for complex gateways + - [ ] Keyboard accessible (tab, enter) + - [ ] Mobile responsive + +- [ ] Create `src/components/settings/GatewaySettingsModal.tsx` + - [ ] Modal wrapper for gateway settings + - [ ] Renders GenericGatewayForm or custom component + - [ ] Save/Cancel buttons + - [ ] Dirty state detection + - [ ] Confirm before closing if unsaved changes + +- [ ] Create `src/components/settings/WebhookHelper.tsx` + - [ ] Display webhook URL + - [ ] Copy to clipboard button + - [ ] Instructions for common gateways (Stripe, PayPal) + - [ ] Test webhook button (if gateway supports) + +### Testing & Quality +- [ ] ESLint: 0 errors, 0 warnings +- [ ] TypeScript: strict mode passes +- [ ] Test with WooCommerce not installed +- [ ] Test with no gateways registered +- [ ] Test with 1 gateway +- [ ] Test with 10+ gateways +- [ ] Test enable/disable toggle +- [ ] Test save settings (valid data) +- [ ] Test save settings (invalid data) +- [ ] Test with Stripe gateway installed +- [ ] Test with PayPal gateway installed +- [ ] Test with custom gateway +- [ ] Test in all 3 admin modes (normal, fullscreen, standalone) +- [ ] Test keyboard navigation +- [ ] Test mobile responsive +- [ ] Performance: API response < 500ms +- [ ] Performance: Settings save < 2s + +--- + +## Phase 2: Custom Gateway UIs ⏳ + +### Stripe Custom UI +- [ ] Create `src/components/settings/gateways/StripeSettings.tsx` + - [ ] Categorized tabs: Basic, API Keys, Advanced + - [ ] Live/Test mode toggle with visual indicator + - [ ] API key validation (format check) + - [ ] Webhook URL helper + - [ ] Test connection button + - [ ] Supported payment methods checklist + - [ ] 3D Secure settings + - [ ] Statement descriptor preview + +### PayPal Custom UI +- [ ] Create `src/components/settings/gateways/PayPalSettings.tsx` + - [ ] Categorized tabs: Basic, API, Advanced + - [ ] Sandbox/Live mode toggle + - [ ] Client ID & Secret fields + - [ ] Webhook URL helper + - [ ] Test connection button + - [ ] PayPal button customization preview + - [ ] Invoice prefix settings + +### Gateway Registry +- [ ] Create `src/lib/gatewayRegistry.ts` + - [ ] Map gateway IDs to custom components + - [ ] Fallback to GenericGatewayForm + - [ ] Feature flags for gradual rollout + +--- + +## Phase 3: Shipping Methods ⏳ + +### Backend - Shipping Provider +- [ ] Create `includes/Compat/ShippingMethodsProvider.php` + - [ ] `get_zones()` - Read from `WC_Shipping_Zones::get_zones()` + - [ ] `get_zone_methods()` - Get methods for each zone + - [ ] `transform_zone()` - Convert WC format to clean JSON + - [ ] `transform_method()` - Convert method settings + - [ ] `get_available_methods()` - List all registered shipping methods + - [ ] Handle methods that don't extend `WC_Shipping_Method` gracefully + +### Backend - REST API Controller +- [ ] Create `includes/API/ShippingController.php` + - [ ] `GET /woonoow/v1/shipping/zones` - List all zones + - [ ] `GET /woonoow/v1/shipping/zones/{id}` - Get single zone + - [ ] `POST /woonoow/v1/shipping/zones/{id}/methods/{method_id}` - Save method + - [ ] `POST /woonoow/v1/shipping/zones/{id}/methods/{method_id}/toggle` - Enable/disable + - [ ] Permission checks + - [ ] Error handling + - [ ] Validation + +### Frontend - Update Shipping Page +- [ ] Update `src/routes/Settings/Shipping.tsx` + - [ ] Replace mock data with API calls + - [ ] Use `useQuery` for fetching zones + - [ ] Use `useMutation` for saving + - [ ] Optimistic updates + - [ ] Refetch on focus + - [ ] Loading/error states + +### UI Components +- [ ] Create `src/components/settings/ShippingZoneCard.tsx` +- [ ] Create `src/components/settings/ShippingMethodCard.tsx` +- [ ] Create `src/components/settings/GenericShippingForm.tsx` + +--- + +## Phase 4: Store Settings ⏳ + +### Backend - Store Settings Provider +- [ ] Create `includes/Compat/StoreSettingsProvider.php` + - [ ] Read WC general settings + - [ ] Read WC product settings + - [ ] Transform to clean format + - [ ] Group by category + +### Backend - REST API +- [ ] Create `includes/API/StoreController.php` + - [ ] `GET /woonoow/v1/store/settings` - Get all store settings + - [ ] `POST /woonoow/v1/store/settings` - Save settings + - [ ] Direct DB operations for performance + - [ ] Validation + +### Frontend - Update Store Page +- [ ] Update `src/routes/Settings/Store.tsx` + - [ ] Replace mock data with API calls + - [ ] Currency selector with live preview + - [ ] Country/timezone auto-detection + - [ ] Address autocomplete + - [ ] Save performance < 2s + +--- + +## Phase 5: Setup Wizard ⏳ + +### Wizard Flow (5 Steps) + +#### Step 1: Store Basics +- [ ] Store name, email, country (required) +- [ ] Auto-detect currency, timezone +- [ ] Industry selector (optional) +- [ ] Save to WC general settings + +#### Step 2: Payments +- [ ] Show recognized gateways if installed +- [ ] Enable manual methods (Bank Transfer, COD) +- [ ] Minimal fields (enable/disable only) +- [ ] Link to full settings for advanced config + +#### Step 3: Shipping +- [ ] Simple flat rate setup +- [ ] Domestic/International zones +- [ ] Free shipping threshold +- [ ] Link to full settings + +#### Step 4: Taxes +- [ ] Auto-calculate toggle +- [ ] Tax rate from country +- [ ] Manual rate option +- [ ] Can skip + +#### Step 5: First Product +- [ ] Create sample product +- [ ] Import CSV +- [ ] Skip option + +### Wizard Components +- [ ] Create `src/routes/Setup/index.tsx` +- [ ] Create `src/routes/Setup/StepProgress.tsx` +- [ ] Create `src/routes/Setup/steps/StoreBasics.tsx` +- [ ] Create `src/routes/Setup/steps/Payments.tsx` +- [ ] Create `src/routes/Setup/steps/Shipping.tsx` +- [ ] Create `src/routes/Setup/steps/Taxes.tsx` +- [ ] Create `src/routes/Setup/steps/FirstProduct.tsx` +- [ ] Create `src/routes/Setup/Complete.tsx` + +### Wizard State +- [ ] Create Zustand store for wizard state +- [ ] Save progress at each step +- [ ] Resume from last step +- [ ] Dashboard banner to resume incomplete setup + +--- + +## Phase 6: Polish & Enhancement ⏳ + +### Error Handling +- [ ] Error boundaries for each gateway/method card +- [ ] Fallback to WC settings on error +- [ ] Helpful error messages +- [ ] Retry mechanisms + +### Performance +- [ ] Monitor API response times +- [ ] Cache gateway/method lists (5 min stale time) +- [ ] Optimistic updates for toggles +- [ ] Direct DB writes where safe +- [ ] Lazy load custom gateway components + +### Accessibility +- [ ] Keyboard navigation (tab, enter, escape) +- [ ] ARIA labels and roles +- [ ] Focus management in modals +- [ ] Screen reader announcements +- [ ] Color contrast compliance + +### Documentation +- [ ] Inline help text for all fields +- [ ] Tooltips for complex options +- [ ] Link to docs for advanced features +- [ ] Video tutorials (future) + +### Analytics +- [ ] Track gateway configuration time +- [ ] Track wizard completion rate +- [ ] Track which gateways are used +- [ ] Track settings save performance + +--- + +# TECHNICAL SPECIFICATIONS + +## API Response Format + +### Payment Gateway +```json +{ + "id": "stripe", + "title": "Stripe Payments", + "description": "Accept Visa, Mastercard, Amex", + "enabled": true, + "type": "provider", + "icon": "credit-card", + "connected": true, + "test_mode": false, + "requirements": { + "met": true, + "missing": [] + }, + "settings": { + "basic": [...], + "api": [...], + "advanced": [...] + }, + "webhook_url": "https://example.com/wc-api/stripe", + "has_custom_ui": true, + "wc_settings_url": "admin.php?page=wc-settings&tab=checkout§ion=stripe" +} +``` + +### Shipping Zone +```json +{ + "id": 1, + "name": "Domestic", + "regions": ["ID"], + "methods": [ + { + "id": "flat_rate:1", + "instance_id": 1, + "title": "Flat Rate", + "enabled": true, + "method_id": "flat_rate", + "settings": {...} + } + ] +} +``` + +## Performance Targets + +| Operation | Target | Current WC | +|-----------|--------|------------| +| Load gateways | < 500ms | ~2s | +| Save gateway settings | < 2s | ~30s | +| Load shipping zones | < 500ms | ~3s | +| Save shipping method | < 2s | ~30s | +| Wizard completion | < 10min | N/A | + +## Browser Support + +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ +- Mobile Safari 14+ +- Chrome Android 90+ + +--- + +# COMPATIBILITY MATRIX + +## Supported Payment Gateways + +| Gateway | Type | Custom UI | Status | +|---------|------|-----------|--------| +| Bank Transfer (BACS) | Manual | ❌ Generic | ✅ Built-in | +| Cash on Delivery | Manual | ❌ Generic | ✅ Built-in | +| Check Payments | Manual | ❌ Generic | ✅ Built-in | +| Stripe | Provider | ✅ Custom | 🔄 Phase 2 | +| PayPal | Provider | ✅ Custom | 🔄 Phase 2 | +| Square | Provider | ❌ Generic | ✅ Auto-support | +| Authorize.net | Provider | ❌ Generic | ✅ Auto-support | +| Other WC-compliant | Any | ❌ Generic | ✅ Auto-support | + +## Supported Shipping Methods + +| Method | Custom UI | Status | +|--------|-----------|--------| +| Flat Rate | ❌ Generic | ✅ Built-in | +| Free Shipping | ❌ Generic | ✅ Built-in | +| Local Pickup | ❌ Generic | ✅ Built-in | +| Table Rate | ❌ Generic | ✅ Auto-support | +| Other WC-compliant | ❌ Generic | ✅ Auto-support | + +--- + +# WIZARD FLOW DETAILS --- diff --git a/includes/Api/PaymentsController.php b/includes/Api/PaymentsController.php new file mode 100644 index 0000000..b8f61f7 --- /dev/null +++ b/includes/Api/PaymentsController.php @@ -0,0 +1,290 @@ +namespace, '/' . $this->rest_base . '/gateways', [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [$this, 'get_gateways'], + 'permission_callback' => [$this, 'check_permission'], + ], + 'schema' => [$this, 'get_gateways_schema'], + ]); + + // GET /woonoow/v1/payments/gateways/{id} + register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways/(?P[a-zA-Z0-9_-]+)', [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [$this, 'get_gateway'], + 'permission_callback' => [$this, 'check_permission'], + 'args' => [ + 'id' => [ + 'description' => 'Gateway ID', + 'type' => 'string', + 'required' => true, + ], + ], + ], + ]); + + // POST /woonoow/v1/payments/gateways/{id} + register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways/(?P[a-zA-Z0-9_-]+)', [ + [ + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => [$this, 'save_gateway'], + 'permission_callback' => [$this, 'check_permission'], + 'args' => [ + 'id' => [ + 'description' => 'Gateway ID', + 'type' => 'string', + 'required' => true, + ], + ], + ], + ]); + + // POST /woonoow/v1/payments/gateways/{id}/toggle + register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways/(?P[a-zA-Z0-9_-]+)/toggle', [ + [ + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => [$this, 'toggle_gateway'], + 'permission_callback' => [$this, 'check_permission'], + 'args' => [ + 'id' => [ + 'description' => 'Gateway ID', + 'type' => 'string', + 'required' => true, + ], + 'enabled' => [ + 'description' => 'Enable or disable gateway', + 'type' => 'boolean', + 'required' => true, + ], + ], + ], + ]); + } + + /** + * Get all payment gateways + * + * @param WP_REST_Request $request Request object + * @return WP_REST_Response|WP_Error Response object or error + */ + public function get_gateways(WP_REST_Request $request) { + try { + $gateways = PaymentGatewaysProvider::get_gateways(); + + $response = rest_ensure_response($gateways); + + // Cache for 5 minutes + $response->header('Cache-Control', 'max-age=300'); + + return $response; + } catch (\Exception $e) { + return new WP_Error( + 'get_gateways_failed', + $e->getMessage(), + ['status' => 500] + ); + } + } + + /** + * Get single payment gateway + * + * @param WP_REST_Request $request Request object + * @return WP_REST_Response|WP_Error Response object or error + */ + public function get_gateway(WP_REST_Request $request) { + $gateway_id = $request->get_param('id'); + + try { + $gateway = PaymentGatewaysProvider::get_gateway($gateway_id); + + if ($gateway === null) { + return new WP_Error( + 'gateway_not_found', + sprintf('Gateway "%s" not found', $gateway_id), + ['status' => 404] + ); + } + + $response = rest_ensure_response($gateway); + + // Cache for 5 minutes + $response->header('Cache-Control', 'max-age=300'); + + return $response; + } catch (\Exception $e) { + return new WP_Error( + 'get_gateway_failed', + $e->getMessage(), + ['status' => 500] + ); + } + } + + /** + * Save gateway settings + * + * @param WP_REST_Request $request Request object + * @return WP_REST_Response|WP_Error Response object or error + */ + public function save_gateway(WP_REST_Request $request) { + $gateway_id = $request->get_param('id'); + $settings = $request->get_json_params(); + + if (empty($settings)) { + return new WP_Error( + 'missing_settings', + 'No settings provided', + ['status' => 400] + ); + } + + try { + $result = PaymentGatewaysProvider::save_gateway_settings($gateway_id, $settings); + + if (is_wp_error($result)) { + return $result; + } + + // Return updated gateway data + $gateway = PaymentGatewaysProvider::get_gateway($gateway_id); + + return rest_ensure_response([ + 'success' => true, + 'message' => 'Gateway settings saved successfully', + 'gateway' => $gateway, + ]); + } catch (\Exception $e) { + return new WP_Error( + 'save_gateway_failed', + $e->getMessage(), + ['status' => 500] + ); + } + } + + /** + * Toggle gateway enabled status + * + * @param WP_REST_Request $request Request object + * @return WP_REST_Response|WP_Error Response object or error + */ + public function toggle_gateway(WP_REST_Request $request) { + $gateway_id = $request->get_param('id'); + $enabled = $request->get_param('enabled'); + + if (!is_bool($enabled)) { + return new WP_Error( + 'invalid_enabled_value', + 'The "enabled" parameter must be a boolean', + ['status' => 400] + ); + } + + try { + $result = PaymentGatewaysProvider::toggle_gateway($gateway_id, $enabled); + + if (is_wp_error($result)) { + return $result; + } + + // Return updated gateway data + $gateway = PaymentGatewaysProvider::get_gateway($gateway_id); + + return rest_ensure_response([ + 'success' => true, + 'message' => $enabled ? 'Gateway enabled' : 'Gateway disabled', + 'gateway' => $gateway, + ]); + } catch (\Exception $e) { + return new WP_Error( + 'toggle_gateway_failed', + $e->getMessage(), + ['status' => 500] + ); + } + } + + /** + * Check permission + * + * @return bool True if user has permission + */ + public function check_permission() { + return current_user_can('manage_woocommerce'); + } + + /** + * Get gateways collection schema + * + * @return array Schema + */ + public function get_gateways_schema() { + return [ + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'payment-gateways', + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'id' => [ + 'description' => 'Gateway ID', + 'type' => 'string', + ], + 'title' => [ + 'description' => 'Gateway title', + 'type' => 'string', + ], + 'description' => [ + 'description' => 'Gateway description', + 'type' => 'string', + ], + 'enabled' => [ + 'description' => 'Whether gateway is enabled', + 'type' => 'boolean', + ], + 'type' => [ + 'description' => 'Gateway type', + 'type' => 'string', + 'enum' => ['manual', 'provider', 'other'], + ], + ], + ], + ]; + } +} diff --git a/includes/Api/Routes.php b/includes/Api/Routes.php index 023cf05..8fe847e 100644 --- a/includes/Api/Routes.php +++ b/includes/Api/Routes.php @@ -7,6 +7,7 @@ use WooNooW\Api\CheckoutController; use WooNooW\Api\OrdersController; use WooNooW\Api\AnalyticsController; use WooNooW\Api\AuthController; +use WooNooW\API\PaymentsController; class Routes { public static function init() { @@ -39,6 +40,10 @@ class Routes { CheckoutController::register(); OrdersController::register(); AnalyticsController::register_routes(); + + // Payments controller + $payments_controller = new PaymentsController(); + $payments_controller->register_routes(); }); } } diff --git a/includes/Compat/PaymentGatewaysProvider.php b/includes/Compat/PaymentGatewaysProvider.php new file mode 100644 index 0000000..ef40a89 --- /dev/null +++ b/includes/Compat/PaymentGatewaysProvider.php @@ -0,0 +1,388 @@ +payment_gateways()->payment_gateways(); + $gateways = []; + + foreach ($wc_gateways as $gateway) { + // Only support gateways that extend WC_Payment_Gateway (respect the bone) + if (!$gateway instanceof WC_Payment_Gateway) { + continue; + } + + $gateways[] = self::transform_gateway($gateway); + } + + return $gateways; + } + + /** + * Get single gateway by ID + * + * @param string $gateway_id Gateway ID + * @return array|null Gateway data or null if not found + */ + public static function get_gateway(string $gateway_id): ?array { + if (!function_exists('WC')) { + return null; + } + + $wc_gateways = WC()->payment_gateways()->payment_gateways(); + + if (!isset($wc_gateways[$gateway_id])) { + return null; + } + + $gateway = $wc_gateways[$gateway_id]; + + if (!$gateway instanceof WC_Payment_Gateway) { + return null; + } + + return self::transform_gateway($gateway); + } + + /** + * Transform WooCommerce gateway to clean format + * + * @param WC_Payment_Gateway $gateway WooCommerce gateway instance + * @return array Clean gateway data + */ + private static function transform_gateway(WC_Payment_Gateway $gateway): array { + $type = self::categorize_gateway($gateway->id); + $requirements = self::check_requirements($gateway); + $settings = self::get_gateway_settings($gateway); + + return [ + 'id' => $gateway->id, + 'title' => $gateway->get_title(), + 'description' => $gateway->get_description(), + 'enabled' => $gateway->enabled === 'yes', + 'type' => $type, + 'icon' => self::get_gateway_icon($gateway->id), + 'method_title' => $gateway->get_method_title(), + 'method_description' => $gateway->get_method_description(), + 'supports' => $gateway->supports, + 'requirements' => $requirements, + 'settings' => $settings, + 'has_fields' => $gateway->has_fields(), + 'countries' => $gateway->countries ?? null, + 'availability' => $gateway->availability ?? 'all', + 'order_button_text' => $gateway->order_button_text ?? null, + 'webhook_url' => self::get_webhook_url($gateway->id), + 'has_custom_ui' => self::has_custom_ui($gateway->id), + 'wc_settings_url' => admin_url('admin.php?page=wc-settings&tab=checkout§ion=' . $gateway->id), + ]; + } + + /** + * Categorize gateway into type + * + * @param string $gateway_id Gateway ID + * @return string Type: manual, provider, or other + */ + private static function categorize_gateway(string $gateway_id): string { + // Manual payment methods + $manual = ['bacs', 'cheque', 'cod']; + if (in_array($gateway_id, $manual, true)) { + return 'manual'; + } + + // Recognized payment providers + $providers = ['stripe', 'paypal', 'stripe_cc', 'ppec_paypal', 'square', 'authorize_net']; + if (in_array($gateway_id, $providers, true)) { + return 'provider'; + } + + // Other WC-compliant gateways + return 'other'; + } + + /** + * Get gateway icon name for UI + * + * @param string $gateway_id Gateway ID + * @return string Icon name (Lucide icon) + */ + private static function get_gateway_icon(string $gateway_id): string { + $icons = [ + 'bacs' => 'banknote', + 'cheque' => 'banknote', + 'cod' => 'banknote', + 'stripe' => 'credit-card', + 'stripe_cc' => 'credit-card', + 'paypal' => 'credit-card', + 'ppec_paypal' => 'credit-card', + 'square' => 'credit-card', + 'authorize_net' => 'credit-card', + ]; + + return $icons[$gateway_id] ?? 'credit-card'; + } + + /** + * Check gateway requirements + * + * @param WC_Payment_Gateway $gateway Gateway instance + * @return array Requirements status + */ + private static function check_requirements(WC_Payment_Gateway $gateway): array { + $requirements = [ + 'met' => true, + 'missing' => [], + ]; + + // Check if gateway has custom requirements method + if (method_exists($gateway, 'get_requirements')) { + $reqs = $gateway->get_requirements(); + if (is_array($reqs)) { + foreach ($reqs as $req => $met) { + if (!$met) { + $requirements['met'] = false; + $requirements['missing'][] = $req; + } + } + } + } + + // Common requirements checks + // Check SSL for gateways that need it + if (in_array('tokenization', $gateway->supports ?? [], true)) { + if (!is_ssl() && get_option('woocommerce_force_ssl_checkout') !== 'yes') { + $requirements['met'] = false; + $requirements['missing'][] = 'SSL certificate required'; + } + } + + return $requirements; + } + + /** + * Get gateway settings fields + * + * @param WC_Payment_Gateway $gateway Gateway instance + * @return array Categorized settings fields + */ + private static function get_gateway_settings(WC_Payment_Gateway $gateway): array { + $form_fields = $gateway->get_form_fields(); + + return [ + 'basic' => self::extract_basic_fields($form_fields), + 'api' => self::extract_api_fields($form_fields), + 'advanced' => self::extract_advanced_fields($form_fields), + ]; + } + + /** + * Extract basic settings fields + * + * @param array $form_fields All form fields + * @return array Basic fields + */ + private static function extract_basic_fields(array $form_fields): array { + $basic_keys = ['enabled', 'title', 'description', 'instructions']; + return self::filter_fields($form_fields, $basic_keys); + } + + /** + * Extract API-related fields + * + * @param array $form_fields All form fields + * @return array API fields + */ + private static function extract_api_fields(array $form_fields): array { + $api_patterns = ['key', 'secret', 'token', 'api', 'client', 'merchant', 'account']; + $api_fields = []; + + foreach ($form_fields as $key => $field) { + foreach ($api_patterns as $pattern) { + if (stripos($key, $pattern) !== false) { + $api_fields[$key] = self::normalize_field($key, $field); + break; + } + } + } + + return $api_fields; + } + + /** + * Extract advanced settings fields + * + * @param array $form_fields All form fields + * @return array Advanced fields + */ + private static function extract_advanced_fields(array $form_fields): array { + $basic_keys = ['enabled', 'title', 'description', 'instructions']; + $advanced_fields = []; + + foreach ($form_fields as $key => $field) { + // Skip basic fields + if (in_array($key, $basic_keys, true)) { + continue; + } + + // Skip API fields (already extracted) + $api_patterns = ['key', 'secret', 'token', 'api', 'client', 'merchant', 'account']; + $is_api = false; + foreach ($api_patterns as $pattern) { + if (stripos($key, $pattern) !== false) { + $is_api = true; + break; + } + } + + if (!$is_api) { + $advanced_fields[$key] = self::normalize_field($key, $field); + } + } + + return $advanced_fields; + } + + /** + * Filter fields by keys + * + * @param array $form_fields All fields + * @param array $keys Keys to extract + * @return array Filtered fields + */ + private static function filter_fields(array $form_fields, array $keys): array { + $filtered = []; + + foreach ($keys as $key) { + if (isset($form_fields[$key])) { + $filtered[$key] = self::normalize_field($key, $form_fields[$key]); + } + } + + return $filtered; + } + + /** + * Normalize field to clean format + * + * @param string $key Field key + * @param array $field Field data + * @return array Normalized field + */ + private static function normalize_field(string $key, array $field): array { + return [ + 'id' => $key, + 'type' => $field['type'] ?? 'text', + 'title' => $field['title'] ?? '', + 'description' => $field['description'] ?? '', + 'default' => $field['default'] ?? '', + 'placeholder' => $field['placeholder'] ?? '', + 'required' => !empty($field['required']), + 'options' => $field['options'] ?? null, + 'custom_attributes' => $field['custom_attributes'] ?? [], + 'class' => $field['class'] ?? '', + ]; + } + + /** + * Get webhook URL for gateway + * + * @param string $gateway_id Gateway ID + * @return string|null Webhook URL or null + */ + private static function get_webhook_url(string $gateway_id): ?string { + // Common webhook URL pattern for WooCommerce + return home_url('/wc-api/' . $gateway_id); + } + + /** + * Check if gateway has custom UI component + * + * @param string $gateway_id Gateway ID + * @return bool True if has custom UI + */ + private static function has_custom_ui(string $gateway_id): bool { + // For now, only Stripe and PayPal will have custom UI (Phase 2) + $custom_ui_gateways = ['stripe', 'paypal', 'stripe_cc', 'ppec_paypal']; + return in_array($gateway_id, $custom_ui_gateways, true); + } + + /** + * Save gateway settings + * + * @param string $gateway_id Gateway ID + * @param array $settings Settings to save + * @return bool|WP_Error True on success, WP_Error on failure + */ + public static function save_gateway_settings(string $gateway_id, array $settings) { + if (!function_exists('WC')) { + return new \WP_Error('wc_not_available', 'WooCommerce is not available'); + } + + $wc_gateways = WC()->payment_gateways()->payment_gateways(); + + if (!isset($wc_gateways[$gateway_id])) { + return new \WP_Error('gateway_not_found', 'Gateway not found'); + } + + $gateway = $wc_gateways[$gateway_id]; + + if (!$gateway instanceof WC_Payment_Gateway) { + return new \WP_Error('invalid_gateway', 'Gateway does not extend WC_Payment_Gateway'); + } + + // Merge with existing settings + $current_settings = get_option($gateway->get_option_key(), []); + $new_settings = array_merge($current_settings, $settings); + + // Use gateway's own validation if available + if (method_exists($gateway, 'validate_settings_fields')) { + $gateway->init_settings(); + foreach ($new_settings as $key => $value) { + $gateway->settings[$key] = $value; + } + $gateway->process_admin_options(); + } else { + // Direct save (faster) + update_option($gateway->get_option_key(), $new_settings, 'yes'); + } + + // Clear cache + wp_cache_delete('woocommerce_payment_gateways', 'options'); + + return true; + } + + /** + * Toggle gateway enabled status + * + * @param string $gateway_id Gateway ID + * @param bool $enabled Enable or disable + * @return bool|WP_Error True on success, WP_Error on failure + */ + public static function toggle_gateway(string $gateway_id, bool $enabled) { + return self::save_gateway_settings($gateway_id, [ + 'enabled' => $enabled ? 'yes' : 'no', + ]); + } +}