__('Shipping', 'formipay'), 'fields' => $shipping_settings ); } return $fields; } /** * Add global shipping settings fields * This implements Phase 3 of the shipping module: Global Shipping Settings */ public function add_global_shipping_settings($fields) { // Load countries from JSON file $countries_json = FORMIPAY_PATH . 'admin/assets/json/country.json'; $countries = file_exists($countries_json) ? json_decode(file_get_contents($countries_json), true) : []; $country_options = []; if (is_array($countries)) { foreach ($countries as $country) { $code = $country['code'] ?? ''; $name = $country['name'] ?? ''; if ($code && $name) { $country_options[$code] = $name; } } } // Store Origin Section $fields['shipping_origin_group'] = array( 'type' => 'group_title', 'label' => __( 'Store Origin', 'formipay' ), 'description' => __( 'Your business location for shipping calculations', 'formipay' ), 'group' => 'started' ); $fields['shipping_origin_country'] = array( 'type' => 'select', 'label' => __( 'Origin Country', 'formipay' ), 'options' => $country_options, 'searchable' => true, 'description' => __( 'Select the country where your products ship from', 'formipay' ), ); $fields['shipping_weight_unit'] = array( 'type' => 'select', 'label' => __( 'Weight Unit', 'formipay' ), 'options' => array( 'kg' => __( 'Kilograms (kg)', 'formipay' ), 'g' => __( 'Grams (g)', 'formipay' ), 'lb' => __( 'Pounds (lb)', 'formipay' ), 'oz' => __( 'Ounces (oz)', 'formipay' ), ), 'value' => 'kg', ); $fields['shipping_dimension_unit'] = array( 'type' => 'select', 'label' => __( 'Dimension Unit', 'formipay' ), 'options' => array( 'cm' => __( 'Centimeters (cm)', 'formipay' ), 'in' => __( 'Inches (in)', 'formipay' ), 'm' => __( 'Meters (m)', 'formipay' ), ), 'value' => 'cm', 'group' => 'ended' ); // Shipping Calculation Method $fields['shipping_calculation_group'] = array( 'type' => 'group_title', 'label' => __( 'Shipping Calculation', 'formipay' ), 'description' => __( 'How shipping costs are calculated for orders', 'formipay' ), 'group' => 'started' ); $fields['shipping_calculation_method'] = array( 'type' => 'select', 'label' => __( 'Calculation Method', 'formipay' ), 'options' => array( 'per_order' => __( 'Per Order (single shipping fee for entire order)', 'formipay' ), 'per_item' => __( 'Per Item (shipping fee multiplied by quantity)', 'formipay' ), ), 'value' => 'per_order', 'group' => 'ended' ); // Supported Destinations Section $fields['shipping_destinations_group'] = array( 'type' => 'group_title', 'label' => __( 'Supported Destinations', 'formipay' ), 'description' => __( 'Configure which countries you ship to and their shipping rates', 'formipay' ), 'group' => 'started' ); // Get enabled currencies for flat rate table $formipay_settings = get_option('formipay_settings', []); $enabled_currencies = []; if (!empty($formipay_settings['multicurrencies']) && is_array($formipay_settings['multicurrencies'])) { foreach ($formipay_settings['multicurrencies'] as $currency) { if (isset($currency['currency'])) { $parts = explode(':::', $currency['currency']); $enabled_currencies[] = [ 'code' => $parts[0] ?? '', 'title' => $parts[1] ?? '', 'symbol' => $parts[2] ?? $parts[0] ?? '', ]; } } } // Fallback to default currency if multicurrency is not enabled if (empty($enabled_currencies)) { $default_currency = $formipay_settings['default_currency'] ?? 'IDR:::Indonesian rupiah:::Rp'; $parts = explode(':::', $default_currency); $enabled_currencies[] = [ 'code' => $parts[0] ?? 'IDR', 'title' => $parts[1] ?? 'Indonesian rupiah', 'symbol' => $parts[2] ?? 'Rp', ]; } // Build currency amount fields for the repeater $currency_fields = []; foreach ($enabled_currencies as $curr) { $code = $curr['code']; $symbol = $curr['symbol']; $currency_fields['flat_rate_' . $code] = array( 'type' => 'number', 'label' => sprintf(__( 'Flat Rate (%s)', 'formipay' ), $code), 'step' => 0.01, 'min' => 0, 'placeholder' => '0.00', ); } // Build free shipping threshold field (use primary currency) $primary_currency = $enabled_currencies[0] ?? []; $primary_symbol = $primary_currency['symbol'] ?? ''; $fields['shipping_destinations'] = array( 'type' => 'repeater', 'label' => __( 'Destinations', 'formipay' ), 'description' => __( 'Add countries you ship to and configure their shipping options', 'formipay' ), 'fields' => array_merge( [ 'country' => array( 'type' => 'select', 'label' => __('Country', 'formipay'), 'options' => $country_options, 'required' => true, 'searchable' => true, 'is_group_title' => true ), 'rate_source' => array( 'type' => 'select', 'label' => __('Rate Source', 'formipay'), 'options' => array( 'flat_rate' => __( 'Flat Rate', 'formipay' ), // 'api' => __( 'Carrier API', 'formipay' ), // Phase 4 ), 'value' => 'flat_rate', ), ], $currency_fields, [ 'free_shipping_threshold' => array( 'type' => 'number', 'label' => sprintf(__( 'Free Shipping Threshold (%s)', 'formipay' ), $primary_symbol), 'description' => __( 'Order amount above which shipping is free. Leave empty to disable.', 'formipay' ), 'step' => 0.01, 'min' => 0, 'placeholder' => 'Empty = disabled', ), ] ) ); // Note: Carrier API settings will be added in Phase 4 $fields['carrier_api_note'] = array( 'type' => 'notification_message', 'description' => __( '

Carrier API Integration

Live carrier rates (Rajaongkir, Biteship, etc.) will be available in Phase 4 of the shipping module.

Currently, only Flat Rate and Free Shipping methods are available at the product level.

', 'formipay' ), ); return $fields; } /** * Add shipping configuration to form settings * This replaces product-level shipping with form-level shipping */ public function add_form_shipping_config($fields) { $shipping_methods = apply_filters( 'formipay/form-settings/tab:shipping/method', [ 'no_shipping' => [ 'method' => __( 'No Shipping Required', 'formipay' ) ], 'flat_rate' => [ 'method' => __( 'Flat Rate', 'formipay' ) ], 'free_shipping' => [ 'method' => __( 'Free Shipping', 'formipay' ) ] ] ); $shipping_options = []; foreach($shipping_methods as $id => $shipping){ $label = $shipping['method']; if(isset($shipping['courier'])){ $label .= ' - '.$shipping['courier']; if(isset($shipping['service'])){ $label .= ' - '.$shipping['service']; } } $shipping_options[$id] = $label; } // Main shipping configuration group $shipping_config_group = [ 'shipping_enable_group' => [ 'type' => 'group_title', 'label' => __( 'Shipping Configuration', 'formipay' ), 'description' => __( 'Configure shipping options for this form. Shipping will be calculated based on form settings, not per-product.', 'formipay' ), 'group' => 'started' ], 'shipping_enabled' => [ 'type' => 'radio', 'label' => __( 'Shipping Method', 'formipay' ), 'options' => $shipping_options, 'value' => 'no_shipping', 'description' => __( 'Select how shipping should be handled for orders from this form', 'formipay' ), ] ]; $shipping_config_group = apply_filters( 'formipay/form-settings/tab:shipping/group:config', $shipping_config_group ); $last_config_key = array_key_last($shipping_config_group); $shipping_config_group[$last_config_key]['group'] = 'ended'; // Apply carrier-specific settings (Flat Rate, etc.) $carrier_settings = apply_filters( 'formipay/form-settings/tab:shipping', [] ); $all_shipping_fields = array_merge($shipping_config_group, $carrier_settings); $fields['formipay_form_settings']['shipping'] = [ 'name' => __( 'Shipping', 'formipay' ), 'fields' => $all_shipping_fields ]; return $fields; } /** * ============================================= * PHASE 4: CARRIER EXTENSION HOOKS * ============================================= */ /** * Add carrier API settings to global shipping settings * Carriers can hook into `formipay/global-settings/tab:shipping/carriers` to add their API key fields * * @param array $fields Existing shipping settings fields * @return array Updated fields with carrier API settings */ public function add_carrier_api_settings($fields) { // Get all registered carriers $carriers = apply_filters('formipay/shipping/carriers', []); if (empty($carriers)) { // No carriers registered, add info message $fields['carrier_api_info'] = array( 'type' => 'notification_message', 'description' => __( '

Carrier API Integration

To enable live shipping rates, install a carrier extension plugin.

Extensions register themselves via the formipay/shipping/carriers filter.

', 'formipay' ), ); return $fields; } // Add carrier API settings section $fields['carrier_api_group'] = array( 'type' => 'group_title', 'label' => __( 'Carrier API Keys', 'formipay' ), 'description' => __( 'Configure API credentials for live shipping rate calculation', 'formipay' ), 'group' => 'started' ); // Allow carriers to inject their API key fields $carrier_fields = apply_filters('formipay/global-settings/tab:shipping/carriers', []); foreach ($carrier_fields as $key => $field) { $fields[$key] = $field; } // Add test connection buttons for each carrier foreach ($carriers as $carrier_id => $carrier) { if (isset($carrier['test_connection']) && $carrier['test_connection']) { $fields['test_connection_' . $carrier_id] = array( 'type' => 'html', 'label' => __( 'Test Connection', 'formipay' ), 'html' => sprintf( ' ', esc_attr($carrier_id), esc_html__('Test Connection', 'formipay') ), 'group' => ($carrier_id === array_key_last($carriers)) ? 'ended' : null, ); } } return $fields; } /** * Get carrier-specific address fields for checkout * Carriers can hook into `formipay/checkout/address-fields/{carrier_id}` to provide their fields * * @param array $fields Current address fields * @param string $carrier_id Carrier identifier (e.g., 'rajaongkir', 'biteship') * @param string $country_code Destination country code * @return array Address fields for this carrier */ public function get_carrier_address_fields($fields, $carrier_id, $country_code) { // Get carrier-specific address fields $carrier_fields = apply_filters('formipay/checkout/address-fields/' . $carrier_id, [], $country_code); return array_merge($fields, $carrier_fields); } /** * AJAX handler for testing carrier connection * Carriers can hook into `formipay/test_carrier_connection/{carrier_id}` to handle the test */ public function ajax_test_carrier_connection() { check_ajax_referer('formipay-admin', 'nonce', true); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Unauthorized', 'formipay')]); } $carrier_id = isset($_POST['carrier']) ? sanitize_text_field(wp_unslash($_POST['carrier'])) : ''; if (empty($carrier_id)) { wp_send_json_error(['message' => __('Missing carrier ID', 'formipay')]); } // Allow carriers to handle their own connection test $result = apply_filters('formipay/test_carrier_connection/' . $carrier_id, [ 'success' => false, 'message' => __('Carrier does not implement connection test', 'formipay'), ], $_POST); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } /** * Helper method: Get registered carriers * Returns all carriers that have registered via the filter * * @return array Registered carriers */ public static function get_registered_carriers() { return apply_filters('formipay/shipping/carriers', []); } /** * Helper method: Get carrier by ID * * @param string $carrier_id Carrier identifier * @return array|null Carrier data or null if not found */ public static function get_carrier($carrier_id) { $carriers = self::get_registered_carriers(); return $carriers[$carrier_id] ?? null; } /** * Helper method: Check if carrier supports a country * * @param string $carrier_id Carrier identifier * @param string $country_code Country code to check * @return bool True if carrier supports the country */ public static function carrier_supports_country($carrier_id, $country_code) { $carrier = self::get_carrier($carrier_id); if (!$carrier) { return false; } $supported_countries = $carrier['countries'] ?? []; return in_array($country_code, $supported_countries, true); } /** * Helper method: Fetch live rates from carrier * * @param string $carrier_id Carrier identifier * @param array $params Rate request parameters (origin, destination, weight, dimensions) * @return array Available rates with costs */ public static function fetch_live_rates($carrier_id, $params) { $default_params = [ 'origin_country' => '', 'origin_city' => '', 'origin_postcode' => '', 'destination_country' => '', 'destination_city' => '', 'destination_postcode' => '', 'weight' => 0, 'weight_unit' => 'kg', 'length' => 0, 'width' => 0, 'height' => 0, 'dimension_unit' => 'cm', ]; $params = wp_parse_args($params, $default_params); return apply_filters('formipay/shipping/live-rates', [], $carrier_id, $params); } /** * ============================================= * PHASE 5: CHECKOUT INTEGRATION * ============================================= */ /** * AJAX: Get available shipping methods for a form * Now reads from form-level shipping settings instead of global settings */ public function ajax_get_shipping_methods() { check_ajax_referer('formipay-public', 'nonce', true); $form_id = isset($_POST['form_id']) ? intval($_POST['form_id']) : 0; $country_code = isset($_POST['country']) ? sanitize_text_field(wp_unslash($_POST['country'])) : ''; $currency = isset($_POST['currency']) ? sanitize_text_field(wp_unslash($_POST['currency'])) : ''; if (!$form_id) { wp_send_json_error(['message' => __('Invalid request', 'formipay')]); } // Get form shipping settings $form_settings = get_post_meta($form_id, 'formipay_form_settings', true); $shipping_enabled = $form_settings['shipping_enabled'] ?? 'no_shipping'; $available_methods = []; if ($shipping_enabled === 'flat_rate') { // Get flat rate from form settings $currency_code = $currency ?: 'IDR'; $rate_key = 'flat_rate_amount_' . $currency_code; $flat_rate = floatval($form_settings[$rate_key] ?? 0); $flat_rate_type = $form_settings['flat_rate_type'] ?? 'fixed'; // For percentage, we'll calculate on frontend based on cart total // For now, store the type so frontend knows how to handle it $available_methods[] = [ 'id' => 'flat_rate', 'name' => __('Standard Shipping', 'formipay'), 'description' => $flat_rate_type === 'percentage' ? sprintf(__('%s%% of order total', 'formipay'), $flat_rate) : __('Delivery in 3-5 business days', 'formipay'), 'cost' => $flat_rate, 'currency' => $currency_code, 'type' => $flat_rate_type, ]; } elseif ($shipping_enabled === 'free_shipping') { // Free shipping from form settings $free_label = $form_settings['free_shipping_label'] ?? __('Free Shipping', 'formipay'); $available_methods[] = [ 'id' => 'free_shipping', 'name' => $free_label, 'description' => __('No shipping cost', 'formipay'), 'cost' => 0, 'currency' => $currency_code ?? '', ]; } // If country-specific shipping is needed in future, add check here // For now, form-level shipping applies to all countries if (empty($available_methods)) { wp_send_json_error([ 'message' => __('Shipping is not available for this form', 'formipay'), 'methods' => [] ]); } wp_send_json_success([ 'methods' => $available_methods, 'default_method' => $available_methods[0]['id'], ]); } /** * Add shipping cost to cart calculation * Hooked into formipay/checkout/cart/calculation */ public function add_shipping_to_cart($cart, $form_id, $selected_currency) { // Check if shipping is enabled for this form $form_settings = get_post_meta($form_id, 'formipay_form_settings', true); $shipping_enabled = $form_settings['shipping_enabled'] ?? 'no_shipping'; if ($shipping_enabled === 'no_shipping') { return $cart; } // Get selected shipping method from POST data or session $shipping_method = isset($_POST['shipping_method']) ? sanitize_text_field(wp_unslash($_POST['shipping_method'])) : ''; $shipping_country = isset($_POST['shipping_country']) ? sanitize_text_field(wp_unslash($_POST['shipping_country'])) : ''; if (empty($shipping_method) || empty($shipping_country)) { return $cart; } // Parse shipping method ID to get cost // Format: flat_rate, free_shipping, or {carrier}_{service} if ($shipping_method === 'free_shipping') { // Free shipping $cart['shipping'] = [ 'name' => __('Free Shipping', 'formipay'), 'cost' => 0, ]; } elseif ($shipping_method === 'flat_rate') { // Flat rate - get cost from form settings $currency_code = $selected_currency ?: 'IDR'; $rate_key = 'flat_rate_amount_' . $currency_code; $flat_rate = floatval($form_settings[$rate_key] ?? 0); // Check if percentage $flat_rate_type = $form_settings['flat_rate_type'] ?? 'fixed'; if ($flat_rate_type === 'percentage') { $subtotal = floatval($cart['subtotal'] ?? 0); $flat_rate = ($subtotal * $flat_rate) / 100; } $cart['shipping'] = [ 'name' => __('Standard Shipping', 'formipay'), 'cost' => $flat_rate, ]; // Recalculate totals $cart['subtotal'] = floatval($cart['subtotal'] ?? 0); $cart['tax'] = floatval($cart['tax'] ?? 0); $cart['discount'] = floatval($cart['discount'] ?? 0); $cart['grand'] = $cart['subtotal'] + $cart['tax'] + $cart['shipping']['cost'] - $cart['discount']; } return $cart; } /** * Add shipping data to order submission * Hooked into formipay/order/process-data */ public function add_shipping_to_order_data($form_data, $form_id) { // Check if shipping is enabled for this form $form_settings = get_post_meta($form_id, 'formipay_form_settings', true); $shipping_enabled = $form_settings['shipping_enabled'] ?? 'no_shipping'; if ($shipping_enabled === 'no_shipping') { return $form_data; } // Add shipping info to form data if (isset($_POST['shipping_method'])) { $form_data['shipping_method'] = sanitize_text_field(wp_unslash($_POST['shipping_method'])); } if (isset($_POST['shipping_country'])) { $form_data['shipping_country'] = sanitize_text_field(wp_unslash($_POST['shipping_country'])); } return $form_data; } /** * AJAX: Get supported shipping countries * With form-level shipping, returns all available countries */ public function ajax_get_supported_countries() { // Verify nonce if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'formipay_public_nonce')) { wp_send_json_error(['message' => __('Invalid security token', 'formipay')]); } $form_id = isset($_POST['form_id']) ? intval($_POST['form_id']) : 0; if (!$form_id) { wp_send_json_error(['message' => __('Invalid form ID', 'formipay')]); } // Load countries from JSON file $countries_json = FORMIPAY_PATH . 'admin/assets/json/country.json'; $all_countries = file_exists($countries_json) ? json_decode(file_get_contents($countries_json), true) : []; // Build country list $countries = []; if (is_array($all_countries)) { foreach ($all_countries as $country) { $code = $country['code'] ?? ''; $name = $country['name'] ?? ''; if ($code && $name) { $countries[$code] = $name; } } } if (empty($countries)) { wp_send_json_error([ 'message' => __('No countries available', 'formipay'), 'countries' => [] ]); } wp_send_json_success([ 'countries' => $countries, ]); } }