✅ Issue 1: Modal Not Showing Current Values (FIXED!) Problem: Opening modal showed defaults, not current saved values Root Cause: Backend only sent field.default, not current value Solution: - Backend: Added field.value with current saved value - normalize_field() now includes: value: $current_settings[$key] - Frontend: Use field.value ?? field.default for initial data - GenericGatewayForm initializes with current values Result: ✅ Modal now shows "BNI Virtual Account 2" not "BNI Virtual Account" ✅ Issue 2: Sticky Modal Footer (FIXED!) Problem: Footer scrolls away with long forms Solution: - Restructured modal: header + scrollable body + sticky footer - DialogContent: flex flex-col with overflow on body only - Footer: sticky bottom-0 with border-t - Save button triggers form.requestSubmit() Result: ✅ Cancel, View in WooCommerce, Save always visible ✅ Issue 3: HTML in Descriptions (FIXED!) Problem: TriPay icon shows as raw HTML string Solution: - Changed: {field.description} - To: dangerouslySetInnerHTML={{ __html: field.description }} - Respects vendor creativity (images, formatting, links) Result: ✅ TriPay icon image renders properly 📋 Technical Details: Backend Changes (PaymentGatewaysProvider.php): - get_gateway_settings() passes $current_settings to extractors - normalize_field() adds 'value' => $current_settings[$key] - All fields now have both default and current value Frontend Changes: - GatewayField interface: Added value?: string | boolean - GenericGatewayForm: Initialize with field.value - Modal structure: Header + Body (scroll) + Footer (sticky) - Descriptions: Render as HTML with dangerouslySetInnerHTML Files Modified: - PaymentGatewaysProvider.php: Add current values to fields - Payments.tsx: Restructure modal layout + add value to interface - GenericGatewayForm.tsx: Use field.value + sticky footer + HTML descriptions 🎯 Result: ✅ Modal shows current saved values ✅ Footer always visible (no scrolling) ✅ Vendor HTML/images render properly
421 lines
14 KiB
PHP
421 lines
14 KiB
PHP
<?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;
|
|
}
|
|
|
|
// Force gateway to reload settings from database
|
|
$gateway->init_settings();
|
|
|
|
$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;
|
|
}
|
|
|
|
// Force gateway to reload settings from database
|
|
$gateway->init_settings();
|
|
|
|
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();
|
|
$current_settings = $gateway->settings; // Get current saved values
|
|
|
|
return [
|
|
'basic' => self::extract_basic_fields($form_fields, $current_settings),
|
|
'api' => self::extract_api_fields($form_fields, $current_settings),
|
|
'advanced' => self::extract_advanced_fields($form_fields, $current_settings),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 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 $current_settings): array {
|
|
$basic_keys = ['enabled', 'title', 'description', 'instructions'];
|
|
return self::filter_fields($form_fields, $basic_keys, $current_settings);
|
|
}
|
|
|
|
/**
|
|
* 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 $current_settings): 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, $current_settings);
|
|
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 $current_settings): 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, $current_settings);
|
|
}
|
|
}
|
|
|
|
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 $current_settings): array {
|
|
$filtered = [];
|
|
|
|
foreach ($keys as $key) {
|
|
if (isset($form_fields[$key])) {
|
|
$filtered[$key] = self::normalize_field($key, $form_fields[$key], $current_settings);
|
|
}
|
|
}
|
|
|
|
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 $current_settings): array {
|
|
// Use current value if available, otherwise use default
|
|
$current_value = $current_settings[$key] ?? $field['default'] ?? '';
|
|
|
|
return [
|
|
'id' => $key,
|
|
'type' => $field['type'] ?? 'text',
|
|
'title' => $field['title'] ?? '',
|
|
'description' => $field['description'] ?? '',
|
|
'default' => $field['default'] ?? '',
|
|
'value' => $current_value, // Add current value!
|
|
'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');
|
|
}
|
|
|
|
// Block external HTTP requests (analytics, tracking, etc.)
|
|
add_filter('pre_http_request', '__return_true', 999);
|
|
|
|
// Get current settings and merge with new ones
|
|
$gateway->init_settings();
|
|
$current_settings = $gateway->settings;
|
|
$new_settings = array_merge($current_settings, $settings);
|
|
|
|
// Debug logging
|
|
error_log(sprintf('[WooNooW] Saving gateway %s settings: %s', $gateway_id, json_encode($settings)));
|
|
error_log(sprintf('[WooNooW] Current enabled: %s, New enabled: %s',
|
|
isset($current_settings['enabled']) ? $current_settings['enabled'] : 'not set',
|
|
isset($new_settings['enabled']) ? $new_settings['enabled'] : 'not set'
|
|
));
|
|
|
|
// Update gateway settings directly
|
|
$gateway->settings = $new_settings;
|
|
|
|
// Save to database using WooCommerce's method
|
|
$saved = update_option($gateway->get_option_key(), $gateway->settings, 'yes');
|
|
error_log(sprintf('[WooNooW] update_option returned: %s', $saved ? 'true' : 'false'));
|
|
|
|
// Update the enabled property specifically (WooCommerce does this)
|
|
if (isset($new_settings['enabled'])) {
|
|
$gateway->enabled = $new_settings['enabled'];
|
|
error_log(sprintf('[WooNooW] Set gateway->enabled to: %s', $gateway->enabled));
|
|
}
|
|
|
|
// Re-enable HTTP requests
|
|
remove_filter('pre_http_request', '__return_true', 999);
|
|
|
|
// Clear all WooCommerce caches
|
|
wp_cache_delete('woocommerce_payment_gateways', 'options');
|
|
wp_cache_flush();
|
|
|
|
// Force WooCommerce to reload payment gateways
|
|
if (function_exists('WC') && WC()->payment_gateways()) {
|
|
WC()->payment_gateways()->init();
|
|
}
|
|
|
|
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',
|
|
]);
|
|
}
|
|
}
|