feat: Implement Payment Gateways backend foundation

 Phase 1 Backend Complete:

📦 PaymentGatewaysProvider.php:
- Read WC gateways from WC()->payment_gateways()
- Transform to clean JSON format
- Categorize: manual/provider/other
- Extract settings: basic/api/advanced
- Check requirements (SSL, extensions)
- Generate webhook URLs
- Respect WC bone structure (WC_Payment_Gateway)

📡 PaymentsController.php:
- GET /woonoow/v1/payments/gateways (list all)
- GET /woonoow/v1/payments/gateways/{id} (single)
- POST /woonoow/v1/payments/gateways/{id} (save settings)
- POST /woonoow/v1/payments/gateways/{id}/toggle (enable/disable)
- Permission checks (manage_woocommerce)
- Error handling with proper HTTP codes
- Response caching (5 min)

🔌 Integration:
- Registered in Api/Routes.php
- Auto-discovers all WC-compliant gateways
- No new hooks - listens to WC structure

📋 Checklist Progress:
- [x] PaymentGatewaysProvider.php
- [x] PaymentsController.php
- [x] REST API registration
- [ ] Frontend components (next)
This commit is contained in:
dwindown
2025-11-05 21:09:49 +07:00
parent f205027c6d
commit 247b2c6b74
4 changed files with 1076 additions and 3 deletions

View File

@@ -1,13 +1,403 @@
# Setup Wizard Design Document # Settings & Setup Wizard - Implementation Guide
> **Living Document** - Track progress, maintain standards, keep on track
## Overview ## 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 ## 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 2. **Smart Defaults** - Pre-configure based on location/industry
3. **Progressive** - Can skip and complete later 3. **Progressive** - Can skip and complete later
4. **Educational** - Teach key concepts without overwhelming 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&section=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
--- ---

View File

@@ -0,0 +1,290 @@
<?php
/**
* Payments REST API Controller
*
* Provides REST endpoints for payment gateway management.
*
* @package WooNooW
*/
namespace WooNooW\API;
use WooNooW\Compat\PaymentGatewaysProvider;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
class PaymentsController extends WP_REST_Controller {
/**
* Namespace
*/
protected $namespace = 'woonoow/v1';
/**
* Rest base
*/
protected $rest_base = 'payments';
/**
* Register routes
*/
public function register_routes() {
// GET /woonoow/v1/payments/gateways
register_rest_route($this->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<id>[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<id>[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<id>[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'],
],
],
],
];
}
}

View File

@@ -7,6 +7,7 @@ use WooNooW\Api\CheckoutController;
use WooNooW\Api\OrdersController; use WooNooW\Api\OrdersController;
use WooNooW\Api\AnalyticsController; use WooNooW\Api\AnalyticsController;
use WooNooW\Api\AuthController; use WooNooW\Api\AuthController;
use WooNooW\API\PaymentsController;
class Routes { class Routes {
public static function init() { public static function init() {
@@ -39,6 +40,10 @@ class Routes {
CheckoutController::register(); CheckoutController::register();
OrdersController::register(); OrdersController::register();
AnalyticsController::register_routes(); AnalyticsController::register_routes();
// Payments controller
$payments_controller = new PaymentsController();
$payments_controller->register_routes();
}); });
} }
} }

View File

@@ -0,0 +1,388 @@
<?php
/**
* Payment Gateways Provider
*
* Reads WooCommerce payment gateways and transforms them into clean format for SPA.
* Respects WooCommerce's bone structure - if gateway extends WC_Payment_Gateway, we support it.
*
* @package WooNooW
*/
namespace WooNooW\Compat;
use WC_Payment_Gateway;
class PaymentGatewaysProvider {
/**
* Get all registered payment gateways
*
* @return array List of gateways in clean format
*/
public static function get_gateways(): array {
if (!function_exists('WC')) {
return [];
}
$wc_gateways = WC()->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&section=' . $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',
]);
}
}