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:
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
290
includes/Api/PaymentsController.php
Normal file
290
includes/Api/PaymentsController.php
Normal 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'],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
388
includes/Compat/PaymentGatewaysProvider.php
Normal file
388
includes/Compat/PaymentGatewaysProvider.php
Normal 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§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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user