Files
WooNooW/includes/Compat/PaymentGatewaysProvider.php
dwindown da241397a5 fix: Add full BACS bank account repeater support + gitignore references
1. Added references/ to .gitignore 

   Folder contains WooCommerce gateway examples:
   - bacs/class-wc-gateway-bacs.php
   - cheque/class-wc-gateway-cheque.php
   - cod/class-wc-gateway-cod.php
   - paypal/ (15 files)
   - currencies.json
   - flags.json

   Purpose: Development reference only, not for production

2. Fixed BACS Bank Account Repeater 

   Problem: Field type was 'account_details' not 'account'
   Solution: Added support for both field types

   Backend changes:
   - PaymentGatewaysProvider.php:
     * Exclude 'account_details' from API fields
     * Load accounts from 'woocommerce_bacs_accounts' option
     * Save accounts to separate option (WC standard)
     * Add default title/description for account_details

   Frontend changes:
   - GenericGatewayForm.tsx:
     * Support both 'account' and 'account_details' types
     * Handle case fallthrough for both types

   Data flow:
   GET: woocommerce_bacs_accounts → account_details.value
   POST: account_details → woocommerce_bacs_accounts

3. How BACS Works in WooCommerce 

   Field structure:
   {
     id: 'account_details',
     type: 'account_details',
     title: 'Account Details',
     description: '...',
     value: [
       {
         account_name: 'Business Account',
         account_number: '12345678',
         bank_name: 'Bank Central Asia',
         sort_code: '001',
         iban: '',
         bic: ''
       }
     ]
   }

   Storage:
   - Settings: woocommerce_bacs_settings (title, description, etc.)
   - Accounts: woocommerce_bacs_accounts (separate option)

   Why separate? WooCommerce uses custom save logic for accounts

4. Now Working 

   When you open BACS settings modal:
    Account Details section appears
    Shows existing bank accounts
    Add/remove accounts with repeater UI
    Save updates woocommerce_bacs_accounts
    Data persists correctly

   UI features:
   - 6 fields per account (3 required, 3 optional)
   - 2-column responsive grid
   - Add/remove buttons
   - Compact card layout

Files Modified:
- .gitignore: Added references/
- PaymentGatewaysProvider.php: BACS special handling
- GenericGatewayForm.tsx: account_details support

Result:
🎉 Bank account repeater now fully functional for BACS!
2025-11-06 13:28:42 +07:00

442 lines
15 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&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();
$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) {
// Skip account_details - it's a special BACS field, not an API field
if ($key === 'account_details') {
continue;
}
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 {
// Special handling for BACS account_details
if ($key === 'account_details') {
$accounts = get_option('woocommerce_bacs_accounts', []);
$current_value = $accounts;
} else {
// 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'] ?? 'Account Details',
'description' => $field['description'] ?? 'Bank account details that will be displayed on the thank you page and in emails.',
'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;
// Special handling for BACS account_details
if ($gateway_id === 'bacs' && isset($settings['account_details'])) {
$accounts = $settings['account_details'];
// Save to separate option like WooCommerce does
update_option('woocommerce_bacs_accounts', $accounts);
// Remove from settings array as it's not stored there
unset($settings['account_details']);
}
$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',
]);
}
}