feat: migrate shipping to form-level and integrate flags.json as single source of truth

Shipping Migration:
- Move shipping configuration from product-level to form-level
- Add form shipping tab in form settings (no_shipping, flat_rate, free_shipping)
- Update FlatRate to register at form level instead of product level
- Update checkout logic to read from form settings
- Support percentage-based flat rate calculation
- Simplify shipping method IDs (flat_rate, free_shipping)

Currency Flags Integration:
- Add formipay_get_all_currency_flags() to read from admin/assets/json/flags.json
- Remove hardcoded CURRENCY_FLAGS emoji map from VariationField.js
- Create CurrencyFlag component to render base64 flag images
- Localize currency_flags to window.formipayProductDetails
- Update shipping info display in admin order details

Benefits:
- Form-level shipping prevents multiplying shipping costs per product
- Single source of truth for currency flags (flags.json)
- Better support for future cart system
- Consistent with e-commerce standards
This commit is contained in:
dwindown
2026-04-23 08:12:40 +07:00
parent 0094a3571c
commit 008188b790
13 changed files with 2819 additions and 797 deletions

View File

@@ -15,8 +15,9 @@ class FlatRate extends Shipping {
parent::__construct();
add_filter( 'formipay/product-config/tab:shipping/method', [$this, 'add_shipping_method'], 15 );
add_filter( 'formipay/product-config/tab:shipping', [$this, 'add_shipping_settings'], 15 );
// Register flat rate as a form-level shipping method
add_filter( 'formipay/form-settings/tab:shipping/method', [$this, 'add_shipping_method'], 15 );
add_filter( 'formipay/form-settings/tab:shipping', [$this, 'add_shipping_settings'], 15 );
// Add to order details
add_filter( 'formipay/order/order-details', [$this, 'add_shipping_to_order_details'], 99, 3 );
@@ -33,113 +34,133 @@ class FlatRate extends Shipping {
}
/**
* Add flat rate settings to form shipping configuration
* These fields are shown when "Flat Rate" is selected as the shipping method
*/
public function add_shipping_settings($fields) {
// Get global currencies configuration
$global_currencies = get_global_currency_array();
// Basic flat rate fields (type and label)
$flat_rate_fields = array(
$this->shipping_method.'_group' => array(
'type' => 'group_title',
'label' => __( 'Flat Rate Setup', 'formipay' ),
'description' => __( 'Configure flat rate shipping cost for this form', 'formipay' ),
'dependency' => array(
array(
'key' => 'product_type',
'value' => 'physical',
'section' => 'general'
),
array(
'key' => 'shipping_method',
'value' => 'flat_rate'
)
'key' => 'shipping_enabled',
'value' => 'flat_rate'
),
'dependencies' => '&&',
'group' => 'started'
),
$this->shipping_method.'_type' => array(
'type' => 'select',
'label' => __( 'Type', 'formipay' ),
'options' => array(
'fixed' => __( 'Fixed', 'formipay' ),
'percentage' => __( 'Percentage', 'formipay' )
'fixed' => __( 'Fixed Amount', 'formipay' ),
'percentage' => __( 'Percentage of Order Total', 'formipay' )
),
'value' => 'fixed',
'dependency' => array(
array(
'key' => 'product_type',
'value' => 'physical',
'section' => 'general'
),
array(
'key' => 'shipping_method',
'value' => 'flat_rate'
)
'key' => 'shipping_enabled',
'value' => 'flat_rate'
),
'dependencies' => '&&',
),
$this->shipping_method.'_amount' => array(
'type' => 'number',
'label' => __( 'Amount', 'formipay' ),
'value' => '10',
'dependency' => array(
array(
'key' => 'product_type',
'value' => 'physical',
'section' => 'general'
),
array(
'key' => 'shipping_method',
'value' => 'flat_rate'
)
),
'dependencies' => '&&',
),
$this->shipping_method.'_label' => array(
'type' => 'text',
'label' => __( 'Label', 'formipay' ),
'description' => __( 'This will be shown in Order Review and Order Details', 'formipay' ),
'value' => __( 'Shipping Fee', 'formipay' ),
'dependency' => array(
array(
'key' => 'product_type',
'value' => 'physical',
'section' => 'general'
),
array(
'key' => 'shipping_method',
'value' => 'flat_rate'
)
),
'dependencies' => '&&',
'group' => 'ended'
),
);
// Add per-currency amount fields
foreach ($global_currencies as $currency) {
// Get the currency code (first part of triple) - this is used for meta key suffix
$currency_code = formipay_get_currency_data_by_value($currency['currency'], 'symbol');
$step = ($currency['decimal_digits'] ?? 2) > 0 ? pow(10, -($currency['decimal_digits'] ?? 2)) : 1;
$is_last = ($currency === end($global_currencies));
$flat_rate_fields[$this->shipping_method.'_amount_'.$currency_code] = array(
'type' => 'number',
'label' => sprintf(__( 'Amount (%s)', 'formipay' ), $currency_code),
'description' => $is_last ? __( 'Shipping cost for this form (not per-product)', 'formipay' ) : '',
'step' => $step,
'min' => 0,
'placeholder' => $is_last ? __( 'Enter Amount...', 'formipay' ) : __( 'Auto', 'formipay' ),
'dependency' => array(
'key' => 'shipping_enabled',
'value' => 'flat_rate'
),
'group' => $is_last ? 'ended' : null,
);
}
// Merge fields into the main fields array
foreach($flat_rate_fields as $key => $value){
$fields[$key] = $value;
}
return $fields;
}
/**
* Add shipping cost to order details
*
* @param array $details Order details array
* @param int $form_id Product/form ID
* @param array $order_data Order data from submission
* @return array Updated order details
*/
public function add_shipping_to_order_details( $details, $form_id, $order_data ) {
if( formipay_get_post_meta($form_id, 'product_type') == 'physical' && formipay_get_post_meta($form_id, 'shipping_method')){
if ( formipay_get_post_meta($form_id, 'product_type') == 'physical' && formipay_get_post_meta($form_id, 'shipping_method') == 'flat_rate' ) {
$amount = floatval( formipay_price_format( formipay_get_post_meta( $form_id, 'flat_rate_amount' ) ) );
$flat_rate_type = formipay_get_post_meta($form_id, 'flat_rate_type');
$flat_rate_label = formipay_get_post_meta($form_id, 'flat_rate_label');
if( formipay_get_post_meta($form_id, 'flat_rate_type') == 'percentage' ) {
$price = floatval( formipay_get_post_meta($form_id, 'product_price') );
$calculate = $price * $amount / 100;
// Get the selected currency from request (same way Order class does it)
$currency = isset($_REQUEST['currency']) ? sanitize_text_field( wp_unslash($_REQUEST['currency']) ) : (string) formipay_default_currency('code');
// Get flat rate amount - check for currency-specific first, then fallback to base
$flat_rate_amount = formipay_get_post_meta($form_id, 'flat_rate_amount_' . $currency);
if (empty($flat_rate_amount)) {
$flat_rate_amount = formipay_get_post_meta($form_id, 'flat_rate_amount');
}
$amount = floatval( formipay_price_format($flat_rate_amount) );
// For percentage-based, calculate from actual product price paid
if ( $flat_rate_type == 'percentage' ) {
// Find the actual product price from order details (already currency-aware)
$product_price = 0;
foreach ($details as $item) {
if (isset($item['context']) && $item['context'] == 'product') {
// Use the first product's amount (already in selected currency)
$product_price = floatval($item['amount']);
break;
}
}
// If no product found in details, fallback to lookup by currency
if ($product_price == 0) {
$regular_key = 'setting_product_price_regular_' . $currency;
$sale_key = 'setting_product_price_sale_' . $currency;
$regular_price = formipay_get_post_meta($form_id, $regular_key);
$sale_price = formipay_get_post_meta($form_id, $sale_key);
$product_price = ($sale_price !== '' && $sale_price !== null) ? floatval($sale_price) : floatval($regular_price);
}
$calculate = $product_price * $amount / 100;
$amount = floatval($calculate);
}
$details[] = [
'item' => formipay_get_post_meta($form_id, 'flat_rate_label'),
'item' => $flat_rate_label,
'amount' => $amount,
'subtotal' => $amount
];
}
return $details;
}

View File

@@ -4,6 +4,15 @@ use Formipay\Traits\SingletonTrait;
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Abstract Shipping Class - Core Carrier Extension System
*
* This class provides the hook system for carrier API extensions.
* Carriers like Rajaongkir, Biteship, etc. can extend Formipay shipping
* by registering themselves via the provided hooks.
*
* @phase 4 - Carrier API Extension System
*/
abstract class Shipping {
use SingletonTrait;
@@ -14,8 +23,42 @@ abstract class Shipping {
protected function __construct() {
add_filter( 'formipay/global-settings', [$this, 'add_setting_shipping_menu'], 15 );
add_filter( 'formipay/product-config', [$this, 'add_form_shipping_menu'], 75 );
// Phase 1-3: Core shipping functionality
add_filter( 'formipay/global-settings', [$this, 'add_setting_shipping_menu'], 15 );
add_filter( 'formipay/form-config', [$this, 'add_form_shipping_config'], 75 );
add_filter( 'formipay/global-settings/tab:shipping', [$this, 'add_global_shipping_settings'], 15 );
// Phase 4: Carrier Extension Hooks
// Carrier registration - allows carriers to register themselves
add_filter( 'formipay/shipping/carriers', '__return_empty_array', 5 );
// Carrier API keys - inject into global shipping settings
add_filter( 'formipay/global-settings/tab:shipping', [$this, 'add_carrier_api_settings'], 20 );
// Checkout address fields - inject carrier-specific address fields
add_filter( 'formipay/checkout/shipping-address-fields', [$this, 'get_carrier_address_fields'], 10, 3 );
// Live rate fetching - allow carriers to provide real-time rates
add_filter( 'formipay/shipping/live-rates', '__return_empty_array', 10, 4 );
// AJAX handler for testing carrier connection
add_action( 'wp_ajax_formipay_test_carrier_connection', [$this, 'ajax_test_carrier_connection'], 10 );
add_action( 'wp_ajax_nopriv_formipay_test_carrier_connection', [$this, 'ajax_test_carrier_connection'], 10 );
// Phase 5: Checkout Integration
// AJAX endpoint for getting available shipping methods for checkout
add_action( 'wp_ajax_formipay_get_shipping_methods', [$this, 'ajax_get_shipping_methods'], 10 );
add_action( 'wp_ajax_nopriv_formipay_get_shipping_methods', [$this, 'ajax_get_shipping_methods'], 10 );
// AJAX endpoint for getting supported countries
add_action( 'wp_ajax_formipay_get_supported_countries', [$this, 'ajax_get_supported_countries'], 10 );
add_action( 'wp_ajax_nopriv_formipay_get_supported_countries', [$this, 'ajax_get_supported_countries'], 10 );
// Hook to add shipping cost to cart calculation
add_filter( 'formipay/checkout/cart/calculation', [$this, 'add_shipping_to_cart'], 10, 3 );
// Hook to add shipping data to order submission
add_filter( 'formipay/order/process-data', [$this, 'add_shipping_to_order_data'], 10, 2 );
}
@@ -36,19 +79,212 @@ abstract class Shipping {
}
public function add_form_shipping_menu($fields) {
/**
* Add global shipping settings fields
* This implements Phase 3 of the shipping module: Global Shipping Settings
*/
public function add_global_shipping_settings($fields) {
$shipping_methods = apply_filters( 'formipay/product-settings/tab:shipping/method', [
// 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' => __( '
<h3>Carrier API Integration</h3>
<p>Live carrier rates (Rajaongkir, Biteship, etc.) will be available in Phase 4 of the shipping module.</p>
<p>Currently, only Flat Rate and Free Shipping methods are available at the product level.</p>
', '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 = [];
$shipping_fields = [];
foreach($shipping_methods as $id => $shipping){
// $id = $shipping['id'];
$label = $shipping['method'];
if(isset($shipping['courier'])){
$label .= ' - '.$shipping['courier'];
@@ -59,102 +295,440 @@ abstract class Shipping {
$shipping_options[$id] = $label;
}
$shipping_fields = [
'shipping_notice' => array(
'type' => 'notification_message',
'image' => FORMIPAY_URL . 'admin/assets/img/logistics.png',
'description' => __( '
<h1>No Shipping Method Available</h1>
<p>Shipping methods only for physical product type. If you insist to use shipping method, change your product type first</p>
', 'formipay' ),
'dependency' => array(
'key' => 'product_type',
'value' => 'digital',
'section' => 'general'
),
),
'shipping_method' => array(
'type' => 'radio',
'label' => esc_html__('Shipping Methods', 'formipay'),
'options' => $shipping_options,
'dependency' => array(
'key' => 'product_type',
'value' => 'physical',
'section' => 'general'
),
)
// 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' ),
]
];
$free_shipping_fields = array(
'free_shipping_group' => array(
'type' => 'group_title',
'label' => __( 'Free Shipping Setup', 'formipay' ),
'description' => __( 'Will not add any shipping fee to the order', 'formipay' ),
'dependency' => array(
array(
'key' => 'product_type',
'value' => 'physical',
'section' => 'general'
),
array(
'key' => 'shipping_method',
'value' => 'free_shipping'
)
),
'dependencies' => '&&',
'group' => 'started'
),
'free_shipping_label' => array(
'type' => 'text',
'label' => __( 'Label', 'formipay' ),
'value' => __( 'Free Shipping', 'formipay' ),
'dependency' => array(
array(
'key' => 'product_type',
'value' => 'physical',
'section' => 'general'
),
array(
'key' => 'shipping_method',
'value' => 'free_shipping'
)
),
'dependencies' => '&&',
),
'free_shipping_add_to_order_review' => array(
'type' => 'checkbox',
'label' => __( 'Show in Order Review', 'formipay' ),
'dependency' => array(
array(
'key' => 'product_type',
'value' => 'physical',
'section' => 'general'
),
array(
'key' => 'shipping_method',
'value' => 'free_shipping'
)
),
'dependencies' => '&&',
'group' => 'ended'
),
);
$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';
foreach($free_shipping_fields as $key => $value) {
$shipping_fields[$key] = $value;
// 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' => __( '
<h3>Carrier API Integration</h3>
<p>To enable live shipping rates, install a carrier extension plugin.</p>
<p>Extensions register themselves via the <code>formipay/shipping/carriers</code> filter.</p>
', 'formipay' ),
);
return $fields;
}
$shipping_fields = apply_filters( 'formipay/product-settings/tab:shipping', $shipping_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'
);
if(!empty($shipping_fields)){
$fields['formipay_product_settings']['shipping'] = array(
'name' => __( 'Shipping', 'formipay' ),
'fields' => $shipping_fields
);
// 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(
'<button type="button" class="button formipay-test-connection" data-carrier="%s">%s</button>
<span class="formipay-connection-result" style="margin-left: 10px;"></span>',
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,
]);
}
}