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:
@@ -64,6 +64,28 @@ jQuery(function($){
|
||||
$('#order-total').html(res.total_formatted);
|
||||
$('#order_status').val(res.status);
|
||||
|
||||
// Populate shipping info if available
|
||||
var shippingInfo = [];
|
||||
if(res.form_data){
|
||||
$.each(res.form_data, function(key, data){
|
||||
if(data.name === 'shipping_country' || data.name === 'shipping_method'){
|
||||
shippingInfo.push(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
if(shippingInfo.length > 0){
|
||||
var source = $("#shipping-info-template").html();
|
||||
var template = Handlebars.compile(source);
|
||||
var context = {
|
||||
datas: shippingInfo
|
||||
};
|
||||
var html = template(context);
|
||||
$("#shipping-info-list").html(html);
|
||||
} else {
|
||||
$("#shipping-info-list").addClass('d-none');
|
||||
$('#no-shipping-info').removeClass('d-none');
|
||||
}
|
||||
|
||||
var source = $("#form-data-item-template").html();
|
||||
var template = Handlebars.compile(source);
|
||||
var context = {
|
||||
|
||||
@@ -122,15 +122,21 @@ function get_global_currency_array() {
|
||||
if(false === $ifSingleCurrency){
|
||||
// $currency_sort = [];
|
||||
$default_sort_key = null;
|
||||
// Extract currency code from default_currency for comparison (handles case where default has symbol but multicurrencies don't)
|
||||
$default_currency_code = explode(':::', $default_currency)[0];
|
||||
foreach($global_currencies as $key => $currency){
|
||||
$currency_value = $currency['currency'];
|
||||
if($currency_value === $default_currency){
|
||||
// Compare by currency code only (before first :::)
|
||||
$currency_code = explode(':::', $currency_value)[0];
|
||||
if($currency_code === $default_currency_code){
|
||||
$default_sort_key = $key;
|
||||
}
|
||||
}
|
||||
$currency_sort = [$default_sort_key => $global_currencies[$default_sort_key]];
|
||||
unset($global_currencies[$default_sort_key]);
|
||||
$global_currencies = $currency_sort + $global_currencies;
|
||||
// Convert associative array to indexed array for JavaScript
|
||||
$global_currencies = array_values($global_currencies);
|
||||
}else{
|
||||
if(false === boolval($multicurrency)){
|
||||
$global_currencies = [
|
||||
@@ -176,6 +182,31 @@ function formipay_get_flag_by_currency($currency) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all currency flags from flags.json
|
||||
* Returns an array mapping currency codes to base64 flag images
|
||||
* This is the single source of truth for currency flags - never duplicate this data
|
||||
*
|
||||
* @return array Array of currency code => flag image mapping
|
||||
*/
|
||||
function formipay_get_all_currency_flags() {
|
||||
static $currency_flags = null;
|
||||
|
||||
if ($currency_flags !== null) {
|
||||
return $currency_flags;
|
||||
}
|
||||
|
||||
$json = file_get_contents(FORMIPAY_PATH . 'admin/assets/json/flags.json');
|
||||
$flags = json_decode($json, true);
|
||||
|
||||
$currency_flags = [];
|
||||
foreach ($flags as $item) {
|
||||
$currency_flags[$item['code']] = $item['flag'];
|
||||
}
|
||||
|
||||
return $currency_flags;
|
||||
}
|
||||
|
||||
function formipay_price_format($num = 0, $post_id = 0){
|
||||
|
||||
$decimal_digits = 2;
|
||||
@@ -362,6 +393,14 @@ function formipay_get_order($order_id) {
|
||||
$label = esc_html__( 'Payment Gateway', 'formipay' );
|
||||
break;
|
||||
|
||||
case 'shipping_country':
|
||||
$label = esc_html__( 'Shipping Country', 'formipay' );
|
||||
break;
|
||||
|
||||
case 'shipping_method':
|
||||
$label = esc_html__( 'Shipping Method', 'formipay' );
|
||||
break;
|
||||
|
||||
default:
|
||||
if(!empty($all_fields[$name.'_config'])){
|
||||
$label = $all_fields[$name.'_config']['label'];
|
||||
|
||||
@@ -55,6 +55,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-detail-card shipping-info-card">
|
||||
<div class="card-title mt-3 mb-0"><?php echo esc_html__( 'Shipping Information', 'formipay' ); ?></div>
|
||||
<div class="card mt-1 border-0 rounded-4 shadow-sm">
|
||||
<div class="card-body p-0 placeholder-glow">
|
||||
<ul class="list-group list-group-flush" id="shipping-info-list">
|
||||
<li class="list-group-item">
|
||||
<b><span class="placeholder col-3"></span></b>
|
||||
<p class="mb-0"><span class="placeholder col-8"></span></p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b><span class="placeholder col-3"></span></b>
|
||||
<p class="mb-0"><span class="placeholder col-8"></span></p>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-none" id="no-shipping-info">
|
||||
<p class="text-center text-muted my-3"><?php echo esc_html__( 'No shipping information available', 'formipay' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-detail-card form-data-card">
|
||||
<div class="card-title mt-3 w-100 mb-0 d-flex justify-content-between align-items-center">
|
||||
<?php echo esc_html__( 'Form Data', 'formipay' ); ?>
|
||||
@@ -274,3 +294,11 @@
|
||||
<p class="mb-0">******</p>
|
||||
</li>
|
||||
</script>
|
||||
<script id="shipping-info-template" type="text/x-handlebars-template">
|
||||
{{#each datas as |data|}}
|
||||
<li class="list-group-item px-0">
|
||||
<b class="field-name">{{data.label}}</b>
|
||||
<p class="field-value mt-1 mb-0">{{data.value}}</p>
|
||||
</li>
|
||||
{{/each}}
|
||||
</script>
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-element', 'wp-i18n', 'wp-icons/build/arrow-left', 'wp-icons/build/bell', 'wp-icons/build/message', 'wp-icons/build/trash', 'wp-primitives'), 'version' => '66ba2a0809137b4a1bf3');
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-element', 'wp-i18n', 'wp-icons/build/arrow-left', 'wp-icons/build/bell', 'wp-icons/build/message', 'wp-icons/build/trash'), 'version' => '79dab88e37717bf64790');
|
||||
|
||||
399
build/admin.css
399
build/admin.css
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -33,6 +33,7 @@ class Product {
|
||||
|
||||
add_action( 'wp_ajax_formipay_product_get_currencies', [$this, 'formipay_product_get_currencies'] );
|
||||
add_action( 'wp_ajax_get_product_variables', [$this, 'get_product_variables'] );
|
||||
add_action( 'wp_ajax_get_product_attributes', [$this, 'get_product_attributes'] );
|
||||
|
||||
add_action('save_post', [$this, 'save_product'], 10, 2);
|
||||
|
||||
@@ -85,14 +86,21 @@ class Product {
|
||||
|
||||
public function add_submenu() {
|
||||
|
||||
add_action( 'add_meta_boxes', [$this, 'add_react_metabox'] );
|
||||
add_action( 'admin_footer-post.php', [$this, 'render_react_metabox_template'] );
|
||||
add_action( 'admin_footer-post-new.php', [$this, 'render_react_metabox_template'] );
|
||||
add_action( 'save_post', [$this, 'save_product_metabox_fields'], 10, 2 );
|
||||
|
||||
add_submenu_page(
|
||||
'formipay',
|
||||
__('Products', 'formipay'),
|
||||
__('Products', 'formipay'),
|
||||
'manage_options',
|
||||
'formipay-products',
|
||||
[$this, 'formipay_products'],
|
||||
[$this, 'formipay_product'],
|
||||
2
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
'formipay',
|
||||
__('Categories', 'formipay'),
|
||||
@@ -105,6 +113,10 @@ class Product {
|
||||
|
||||
}
|
||||
|
||||
public function formipay_product() {
|
||||
\Formipay\Admin\ReactAdmin::render_mount_point('products');
|
||||
}
|
||||
|
||||
public function enqueue_admin() {
|
||||
// Assets now handled by ReactAdmin class
|
||||
return;
|
||||
@@ -227,9 +239,71 @@ class Product {
|
||||
|
||||
}
|
||||
|
||||
public function formipay_products_react() {
|
||||
|
||||
ReactAdmin::render_mount_point('products');
|
||||
}
|
||||
|
||||
public function add_react_metabox() {
|
||||
add_meta_box(
|
||||
'formipay_product_settings',
|
||||
__('Settings', 'formipay'),
|
||||
[$this, 'render_react_metabox'],
|
||||
'formipay-product',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
|
||||
public function render_react_metabox($post) {
|
||||
echo '<div data-formipay-field-renderer="product" data-post-id="' . esc_attr($post->ID) . '"></div>';
|
||||
}
|
||||
|
||||
public function render_react_metabox_template() {
|
||||
global $post;
|
||||
|
||||
if (!$post || $post->post_type !== 'formipay-product') {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = \Formipay\Admin\FieldConfigBridge::get_config_for_post($post->ID, $post->post_type);
|
||||
|
||||
// Get multi-currency settings from formipay_settings option
|
||||
$settings = get_option('formipay_settings', []);
|
||||
$is_multicurrency = !empty($settings['enable_multicurrency']);
|
||||
$multicurrencies = $settings['multicurrencies'] ?? [];
|
||||
|
||||
// Build global_selected_currencies from multicurrencies array
|
||||
$global_selected = [];
|
||||
foreach ($multicurrencies as $currency) {
|
||||
if (isset($currency['currency'])) {
|
||||
$code = explode(':::', $currency['currency'])[0];
|
||||
$global_selected[$code] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the same helper functions for consistency
|
||||
$global_currencies = get_global_currency_array();
|
||||
$default_currency = formipay_default_currency();
|
||||
|
||||
$product_details = [
|
||||
'multicurrency' => $is_multicurrency,
|
||||
'default_currency' => $default_currency,
|
||||
'global_currencies' => $global_currencies,
|
||||
'global_selected_currencies' => $global_selected,
|
||||
'currency_flags' => formipay_get_all_currency_flags(),
|
||||
];
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
window.formipayFieldConfig = <?php echo wp_json_encode($config); ?>;
|
||||
window.formipayProductDetails = <?php echo wp_json_encode($product_details); ?>;
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function formipay_products() {
|
||||
// React admin
|
||||
\Formipay\Admin\ReactAdmin::render_mount_point('products');
|
||||
|
||||
ReactAdmin::render_mount_point('products');
|
||||
}
|
||||
|
||||
public function cpt_post_fields_box($boxes) {
|
||||
@@ -248,7 +322,106 @@ class Product {
|
||||
$fields = apply_filters( 'formipay/product-config', $fields );
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function save_product_metabox_fields($post_id, $post) {
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'update-post_' . $post_id)) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
if ($post->post_type !== 'formipay-product') {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
$meta_fields = [
|
||||
'product_type',
|
||||
'setting_product_price_regular',
|
||||
'setting_product_price_sale',
|
||||
'free_shipping',
|
||||
// Shipping fields
|
||||
'shipping_method',
|
||||
'flat_rate_type',
|
||||
'flat_rate_amount',
|
||||
'flat_rate_label',
|
||||
'free_shipping_label',
|
||||
'free_shipping_add_to_order_review',
|
||||
// Shipping dimensions (for carrier API calculation)
|
||||
'product_weight',
|
||||
'product_length',
|
||||
'product_width',
|
||||
'product_height',
|
||||
// Access and status
|
||||
'product_accesses',
|
||||
'product_access_to_email',
|
||||
'active',
|
||||
];
|
||||
|
||||
$global_currencies = get_global_currency_array();
|
||||
|
||||
foreach ($meta_fields as $field) {
|
||||
if (isset($_POST[$field])) {
|
||||
$value = wp_unslash($_POST[$field]);
|
||||
update_post_meta($post_id, $field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($global_currencies as $currency) {
|
||||
$symbol = formipay_get_currency_data_by_value($currency['currency'], 'symbol');
|
||||
|
||||
if (isset($_POST['setting_product_price_regular_' . $symbol])) {
|
||||
update_post_meta($post_id, 'setting_product_price_regular_' . $symbol, wp_unslash($_POST['setting_product_price_regular_' . $symbol]));
|
||||
}
|
||||
|
||||
if (isset($_POST['max_amount_' . $symbol])) {
|
||||
update_post_meta($post_id, 'max_amount_' . $symbol, wp_unslash($_POST['max_amount_' . $symbol]));
|
||||
}
|
||||
|
||||
// Save flat rate amounts per currency
|
||||
if (isset($_POST['flat_rate_amount_' . $symbol])) {
|
||||
update_post_meta($post_id, 'flat_rate_amount_' . $symbol, wp_unslash($_POST['flat_rate_amount_' . $symbol]));
|
||||
}
|
||||
}
|
||||
|
||||
// Save product variations (JSON from VariationField)
|
||||
if (isset($_POST['product_variations'])) {
|
||||
$variations_json = wp_unslash($_POST['product_variations']);
|
||||
$variations_data = json_decode($variations_json, true);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($variations_data)) {
|
||||
update_post_meta($post_id, 'product_variations', $variations_json);
|
||||
|
||||
// Also save the legacy format for backward compatibility
|
||||
if (isset($variations_data['variations']) && is_array($variations_data['variations'])) {
|
||||
$legacy_variations = $variations_data['variations'];
|
||||
update_post_meta($post_id, 'product_variables', wp_json_encode($legacy_variations));
|
||||
}
|
||||
|
||||
// Save attributes separately for legacy compatibility
|
||||
if (isset($variations_data['attributes']) && is_array($variations_data['attributes'])) {
|
||||
$legacy_attributes = [];
|
||||
foreach ($variations_data['attributes'] as $attr) {
|
||||
$legacy_attributes[] = [
|
||||
'attribute_name' => $attr['attribute_name'] ?? '',
|
||||
'attribute_type' => $attr['attribute_type'] ?? 'select',
|
||||
'attribute_variations' => $attr['attribute_variations'] ?? [],
|
||||
];
|
||||
}
|
||||
update_post_meta($post_id, 'product_variation_attributes', wp_json_encode($legacy_attributes));
|
||||
}
|
||||
} else {
|
||||
delete_post_meta($post_id, 'product_variations');
|
||||
}
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
public function general_config($fields) {
|
||||
@@ -434,7 +607,68 @@ class Product {
|
||||
|
||||
$product_currency_group = apply_filters( 'formipay/product-settings/tab:general/group:product-currency', $product_currency_group );
|
||||
|
||||
$general_all_fields = array_merge($product_details_group, $product_currency_group);
|
||||
// Shipping Dimensions Group (for physical products only)
|
||||
$shipping_dimensions_group = array(
|
||||
'setting_product_shipping_dimensions' => array(
|
||||
'type' => 'group_title',
|
||||
'label' => __( 'Shipping Dimensions', 'formipay' ),
|
||||
'description' => __( 'Weight and dimensions for carrier shipping calculation.', 'formipay' ),
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
),
|
||||
'group' => 'started'
|
||||
),
|
||||
'product_weight' => array(
|
||||
'type' => 'number',
|
||||
'label' => __( 'Weight (kg)', 'formipay' ),
|
||||
'step' => 0.01,
|
||||
'min' => 0,
|
||||
'placeholder' => '0.00',
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
)
|
||||
),
|
||||
'product_length' => array(
|
||||
'type' => 'number',
|
||||
'label' => __( 'Length (cm)', 'formipay' ),
|
||||
'step' => 0.1,
|
||||
'min' => 0,
|
||||
'placeholder' => '0.0',
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
)
|
||||
),
|
||||
'product_width' => array(
|
||||
'type' => 'number',
|
||||
'label' => __( 'Width (cm)', 'formipay' ),
|
||||
'step' => 0.1,
|
||||
'min' => 0,
|
||||
'placeholder' => '0.0',
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
)
|
||||
),
|
||||
'product_height' => array(
|
||||
'type' => 'number',
|
||||
'label' => __( 'Height (cm)', 'formipay' ),
|
||||
'step' => 0.1,
|
||||
'min' => 0,
|
||||
'placeholder' => '0.0',
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical'
|
||||
),
|
||||
'group' => 'ended'
|
||||
),
|
||||
);
|
||||
|
||||
$shipping_dimensions_group = apply_filters( 'formipay/product-settings/tab:general/group:shipping-dimensions', $shipping_dimensions_group );
|
||||
|
||||
$general_all_fields = array_merge($product_details_group, $product_currency_group, $shipping_dimensions_group);
|
||||
|
||||
$general_all_fields = apply_filters( 'formipay/product-settings/tab:general', $general_all_fields );
|
||||
|
||||
@@ -448,77 +682,28 @@ class Product {
|
||||
}
|
||||
|
||||
public function variations_config($fields) {
|
||||
// Product Variations Attribute Group
|
||||
$product_attributes_group = array(
|
||||
// Product Variations - Unified React field with attributes and variations table
|
||||
$product_variations_group = array(
|
||||
'setting_product_variations' => array(
|
||||
'type' => 'group_title',
|
||||
'label' => __( 'Attributes', 'formipay' ),
|
||||
'description' => __( 'First we need to build the attribute of this product. For example Color, Size, License Site Count.', 'formipay' ),
|
||||
'label' => __( 'Product Variations', 'formipay' ),
|
||||
'description' => __( 'Configure attributes and their combinations to generate product variations with multi-currency pricing.', 'formipay' ),
|
||||
'group' => 'started'
|
||||
),
|
||||
'product_has_variation' => array(
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Product has variations', 'formipay' ),
|
||||
),
|
||||
'product_variation_attributes' => array(
|
||||
'type' => 'repeater',
|
||||
'label' => __('Attributes', 'formipay'),
|
||||
'description' => __( 'Your attributes will generate variation automatically.', 'formipay' ),
|
||||
'fields' => [
|
||||
'attribute_name' => [
|
||||
'type' => 'text',
|
||||
'label' => __( 'Attribute Name', 'formipay' ),
|
||||
'description' => __( 'e.g. Color, Size, etc', 'formipay' ),
|
||||
'is_group_title' => true
|
||||
],
|
||||
'attribute_variations' => [
|
||||
'type' => 'repeater',
|
||||
'label' => esc_html__( 'Variation', 'formipay' ),
|
||||
'fields' => [
|
||||
'variation_label' => [
|
||||
'type' => 'text',
|
||||
'label' => __( 'Title', 'formipay' ),
|
||||
'description' => __( 'e.g. Red, XL, etc', 'formipay' ),
|
||||
'required' => true,
|
||||
'is_group_title' => true
|
||||
],
|
||||
'variation_value' => [
|
||||
'type' => 'text',
|
||||
'label' => __( 'Value', 'formipay' ),
|
||||
'description' => __( 'e.g. red, xl, etc', 'formipay' ),
|
||||
'required' => true
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
'dependency' => array(
|
||||
'key' => 'product_has_variation',
|
||||
'value' => 'not_empty'
|
||||
),
|
||||
'product_variations' => array(
|
||||
'type' => 'variation',
|
||||
'label' => __( 'Variations', 'formipay' ),
|
||||
'description' => __( 'Add attributes (e.g., Color, Size) and their values. Variations will be generated automatically from all combinations.', 'formipay' ),
|
||||
'name' => 'product_variations',
|
||||
),
|
||||
);
|
||||
|
||||
$product_attributes_group = apply_filters( 'formipay/product-settings/tab:general/group:product-attributes', $product_attributes_group );
|
||||
$product_variations_group = apply_filters( 'formipay/product-settings/tab:variations/group:product-variations', $product_variations_group );
|
||||
|
||||
$last_product_attributes_group = array_key_last($product_attributes_group);
|
||||
$product_attributes_group[$last_product_attributes_group]['group'] = 'ended';
|
||||
$last_product_variations_group = array_key_last($product_variations_group);
|
||||
$product_variations_group[$last_product_variations_group]['group'] = 'ended';
|
||||
|
||||
// Product Variations Attribute Group
|
||||
|
||||
// Define your product variations field group somewhere in your plugin/theme
|
||||
$product_variations_table_html = file_get_contents(FORMIPAY_PATH . 'admin/templates/product-variations.php');
|
||||
|
||||
$product_variations_group = [
|
||||
'variation_table' => [
|
||||
'type' => 'html',
|
||||
'label' => __( 'Variations', 'formipay' ),
|
||||
'html' => $product_variations_table_html
|
||||
],
|
||||
];
|
||||
|
||||
$variation_all_fields = array_merge($product_attributes_group, $product_variations_group);
|
||||
|
||||
$variation_all_fields = apply_filters( 'formipay/product-settings/tab:variations', $variation_all_fields );
|
||||
$variation_all_fields = apply_filters( 'formipay/product-settings/tab:variations', $product_variations_group );
|
||||
|
||||
$fields['formipay_product_settings']['variation'] = array(
|
||||
'name' => __('Variations', 'formipay'),
|
||||
@@ -959,6 +1144,25 @@ class Product {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
public function get_product_attributes() {
|
||||
$post_id = intval($_POST['post_id'] ?? 0);
|
||||
|
||||
// Check permissions
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
wp_send_json_error(['message' => 'Unauthorized']);
|
||||
}
|
||||
|
||||
// Get attributes from legacy meta key
|
||||
$data = get_post_meta($post_id, 'product_variation_attributes', true);
|
||||
$json = is_string($data) ? json_decode($data, true) : $data;
|
||||
|
||||
if (is_array($json)) {
|
||||
wp_send_json_success($json);
|
||||
}
|
||||
|
||||
wp_send_json_success([]);
|
||||
}
|
||||
|
||||
public function save_product_depracated($post_id, $post) {
|
||||
// Verify nonce and permissions here if you have a nonce field (recommended)
|
||||
|
||||
|
||||
@@ -653,6 +653,7 @@ class Render {
|
||||
wp_enqueue_script( 'choices', FORMIPAY_URL . 'vendor/ChoicesJS/choices.min.js', [], FORMIPAY_VERSION, true );
|
||||
wp_enqueue_script( 'formipay-popup', FORMIPAY_URL . 'public/assets/js/popup-action.js', ['jquery', 'choices'], FORMIPAY_VERSION, true);
|
||||
wp_enqueue_script( 'formipay-form', FORMIPAY_URL . 'public/assets/js/form-action.js', ['jquery', 'choices'], FORMIPAY_VERSION, true);
|
||||
wp_enqueue_script( 'formipay-checkout-shipping', FORMIPAY_URL . 'public/assets/js/checkout-shipping.js', ['jquery', 'formipay-form'], FORMIPAY_VERSION, true);
|
||||
// Localize data for all forms
|
||||
$form_data = [
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
@@ -662,6 +663,16 @@ class Render {
|
||||
];
|
||||
wp_localize_script('formipay-form', 'formipay_form', $form_data);
|
||||
|
||||
// Localize shipping labels for checkout
|
||||
$shipping_data = [
|
||||
'labels' => [
|
||||
'country' => __('Shipping Country', 'formipay'),
|
||||
'selectCountry' => __('Select your country', 'formipay'),
|
||||
'shippingMethod' => __('Shipping Method', 'formipay'),
|
||||
]
|
||||
];
|
||||
wp_localize_script('formipay-checkout-shipping', 'formipay_shipping', $shipping_data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -766,6 +777,11 @@ class Render {
|
||||
$allowed_currency_pack = $this->resolve_allowed_currencies($post_id);
|
||||
$currency_code = $allowed_currency_pack['default_code'];
|
||||
$currency_cfg = $this->resolve_currency_config($currency_code);
|
||||
|
||||
// Get form shipping settings
|
||||
$form_settings = get_post_meta($post_id, 'formipay_form_settings', true);
|
||||
$shipping_enabled = $form_settings['shipping_enabled'] ?? 'no_shipping';
|
||||
|
||||
$form_data[$post_id] = [
|
||||
'form_id' => $post_id,
|
||||
'currency' => formipay_post_currency($post_id),
|
||||
@@ -796,6 +812,7 @@ class Render {
|
||||
'static_products' => array_filter(array_map('absint', explode(',', (string) formipay_get_post_meta($post_id, 'static_products')))),
|
||||
'static_items' => json_decode((string) formipay_get_post_meta($post_id, 'static_items'), true) ?: [],
|
||||
'currency_code' => (function($c){ $p = explode(':::', (string)$c); return $p[0] ?? 'IDR'; })(formipay_post_currency($post_id)),
|
||||
'shipping_enabled' => $shipping_enabled, // Form-level shipping setting
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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,85 +34,66 @@ 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',
|
||||
'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',
|
||||
'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;
|
||||
}
|
||||
@@ -120,20 +102,59 @@ class FlatRate extends Shipping {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
];
|
||||
|
||||
@@ -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() {
|
||||
|
||||
// Phase 1-3: Core shipping functionality
|
||||
add_filter( 'formipay/global-settings', [$this, 'add_setting_shipping_menu'], 15 );
|
||||
add_filter( 'formipay/product-config', [$this, 'add_form_shipping_menu'], 75 );
|
||||
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(
|
||||
// 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' => esc_html__('Shipping Methods', 'formipay'),
|
||||
'label' => __( 'Shipping Method', 'formipay' ),
|
||||
'options' => $shipping_options,
|
||||
'dependency' => array(
|
||||
'key' => 'product_type',
|
||||
'value' => 'physical',
|
||||
'section' => 'general'
|
||||
),
|
||||
)
|
||||
'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';
|
||||
|
||||
// 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;
|
||||
|
||||
foreach($free_shipping_fields as $key => $value) {
|
||||
$shipping_fields[$key] = $value;
|
||||
}
|
||||
|
||||
$shipping_fields = apply_filters( 'formipay/product-settings/tab:shipping', $shipping_fields );
|
||||
/**
|
||||
* =============================================
|
||||
* PHASE 4: CARRIER EXTENSION HOOKS
|
||||
* =============================================
|
||||
*/
|
||||
|
||||
if(!empty($shipping_fields)){
|
||||
$fields['formipay_product_settings']['shipping'] = array(
|
||||
'name' => __( 'Shipping', 'formipay' ),
|
||||
'fields' => $shipping_fields
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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(
|
||||
'<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,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
346
public/assets/js/checkout-shipping.js
Normal file
346
public/assets/js/checkout-shipping.js
Normal file
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Formipay Checkout Shipping Integration
|
||||
* Handles country selection, shipping method display, and cost calculation
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
const FormipayCheckoutShipping = {
|
||||
|
||||
selectedCountry: '',
|
||||
selectedMethod: '',
|
||||
availableMethods: [],
|
||||
formId: null,
|
||||
|
||||
init() {
|
||||
this.formId = $('form[data-form-id]').data('form-id');
|
||||
|
||||
if (!this.formId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.bindEvents();
|
||||
this.initializeCountrySelector();
|
||||
},
|
||||
|
||||
bindEvents() {
|
||||
// Country selection change
|
||||
$(document).on('change', '.formipay-shipping-country', (e) => {
|
||||
this.onCountryChange($(e.currentTarget).val());
|
||||
});
|
||||
|
||||
// Shipping method selection change
|
||||
$(document).on('change', '.formipay-shipping-method', (e) => {
|
||||
this.onShippingMethodChange($(e.currentTarget).val());
|
||||
});
|
||||
},
|
||||
|
||||
initializeCountrySelector() {
|
||||
// Check if this form has shipping enabled
|
||||
if (this.isShippingEnabled()) {
|
||||
// Add country selector before payment options
|
||||
this.insertCountrySelector();
|
||||
}
|
||||
},
|
||||
|
||||
isShippingEnabled() {
|
||||
// Check if shipping is enabled for this form
|
||||
// With form-level shipping, we check shipping_enabled setting
|
||||
return window.formipayFormData?.shipping_enabled &&
|
||||
window.formipayFormData.shipping_enabled !== 'no_shipping';
|
||||
},
|
||||
|
||||
insertCountrySelector() {
|
||||
const orderReviewTable = $('#formipay-review-order');
|
||||
|
||||
if (orderReviewTable.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create country selector row before subtotal
|
||||
const countryRow = `
|
||||
<tr class="formipay-shipping-row">
|
||||
<th>
|
||||
<label for="shipping-country-${this.formId}">${formipay_shipping.labels.country || 'Shipping Country'}</label>
|
||||
<select id="shipping-country-${this.formId}"
|
||||
class="formipay-shipping-country"
|
||||
name="shipping_country"
|
||||
required>
|
||||
<option value="">${formipay_shipping.labels.selectCountry || 'Select your country'}</option>
|
||||
</select>
|
||||
</th>
|
||||
<td>
|
||||
<span class="formipay-loading-spinner" style="display:none;">⏳</span>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
orderReviewTable.find('tbody').append(countryRow);
|
||||
|
||||
// Populate countries from settings
|
||||
this.loadCountries();
|
||||
},
|
||||
|
||||
loadCountries() {
|
||||
// Get supported countries from shipping settings
|
||||
$.ajax({
|
||||
url: formipay_admin.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'formipay_get_supported_countries',
|
||||
nonce: formipay_admin.nonce,
|
||||
form_id: this.formId
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success && response.data.countries) {
|
||||
this.populateCountries(response.data.countries);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
// Fallback: show all countries
|
||||
this.populateCountries(this.getAllCountries());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
populateCountries(countries) {
|
||||
const select = $(`#shipping-country-${this.formId}`);
|
||||
select.find('option:not([value=""])').remove();
|
||||
|
||||
$.each(countries, (code, name) => {
|
||||
select.append(`<option value="${code}">${name}</option>`);
|
||||
});
|
||||
},
|
||||
|
||||
getAllCountries() {
|
||||
// Fallback country list
|
||||
return {
|
||||
'ID': 'Indonesia',
|
||||
'MY': 'Malaysia',
|
||||
'SG': 'Singapore',
|
||||
'TH': 'Thailand',
|
||||
'VN': 'Vietnam',
|
||||
'PH': 'Philippines',
|
||||
'TW': 'Taiwan',
|
||||
'HK': 'Hong Kong',
|
||||
'IN': 'India',
|
||||
'CN': 'China',
|
||||
'JP': 'Japan',
|
||||
'KR': 'South Korea',
|
||||
'AU': 'Australia',
|
||||
'NZ': 'New Zealand',
|
||||
'GB': 'United Kingdom',
|
||||
'US': 'United States',
|
||||
'CA': 'Canada',
|
||||
'FR': 'France',
|
||||
'DE': 'Germany',
|
||||
'IT': 'Italy',
|
||||
'ES': 'Spain',
|
||||
'NL': 'Netherlands',
|
||||
'BE': 'Belgium',
|
||||
'CH': 'Switzerland',
|
||||
'AT': 'Austria',
|
||||
'IE': 'Ireland',
|
||||
'DK': 'Denmark',
|
||||
'SE': 'Sweden',
|
||||
'NO': 'Norway',
|
||||
'FI': 'Finland',
|
||||
};
|
||||
},
|
||||
|
||||
onCountryChange(countryCode) {
|
||||
this.selectedCountry = countryCode;
|
||||
|
||||
if (!countryCode) {
|
||||
this.hideShippingMethods();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get available shipping methods for this country
|
||||
this.fetchShippingMethods(countryCode);
|
||||
},
|
||||
|
||||
fetchShippingMethods(countryCode) {
|
||||
const spinner = $('.formipay-loading-spinner');
|
||||
spinner.show();
|
||||
|
||||
$.ajax({
|
||||
url: formipay_admin.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'formipay_get_shipping_methods',
|
||||
nonce: formipay_public.nonce,
|
||||
form_id: this.formId,
|
||||
country: countryCode,
|
||||
currency: formipay.currency_code || 'IDR'
|
||||
},
|
||||
success: (response) => {
|
||||
spinner.hide();
|
||||
|
||||
if (response.success) {
|
||||
this.availableMethods = response.data.methods || [];
|
||||
this.displayShippingMethods(this.availableMethods, response.data.default_method);
|
||||
} else {
|
||||
this.showError(response.data.message || 'Unable to load shipping methods');
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
spinner.hide();
|
||||
this.showError('Unable to connect to shipping service');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
displayShippingMethods(methods, defaultMethod) {
|
||||
// Remove existing shipping method selector if any
|
||||
$('.formipay-shipping-method-row').remove();
|
||||
|
||||
if (methods.length === 0) {
|
||||
this.showError('Shipping is not available for this form');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get order total for percentage calculations
|
||||
const orderTotal = this.getOrderTotal();
|
||||
|
||||
let methodsHtml = '<div class="formipay-shipping-methods">';
|
||||
methodsHtml += `<input type="hidden" name="shipping_method" value="${defaultMethod}" class="formipay-shipping-method-input">`;
|
||||
|
||||
$.each(methods, (index, method) => {
|
||||
const methodId = method.id;
|
||||
const isFree = method.cost === 0;
|
||||
const isPercentage = method.type === 'percentage';
|
||||
|
||||
// Calculate actual cost
|
||||
let actualCost = method.cost;
|
||||
let costDisplay = '';
|
||||
|
||||
if (isFree) {
|
||||
costDisplay = 'FREE';
|
||||
} else if (isPercentage) {
|
||||
actualCost = (orderTotal * method.cost) / 100;
|
||||
costDisplay = `${method.cost}% (${this.formatCost(actualCost, method.currency)})`;
|
||||
} else {
|
||||
costDisplay = this.formatCost(method.cost, method.currency);
|
||||
}
|
||||
|
||||
methodsHtml += `
|
||||
<div class="formipay-shipping-option" data-method="${methodId}">
|
||||
<label class="formipay-shipping-label">
|
||||
<input type="radio"
|
||||
name="shipping_method_display"
|
||||
value="${methodId}"
|
||||
${methodId === defaultMethod ? 'checked' : ''}
|
||||
class="formipay-shipping-method"
|
||||
data-cost="${actualCost}"
|
||||
data-type="${method.type || 'fixed'}"
|
||||
data-base-cost="${method.cost}">
|
||||
<span class="shipping-method-name">${method.name}</span>
|
||||
${isFree ? '<span class="badge badge-free">FREE</span>' : ''}
|
||||
<span class="shipping-method-cost">${costDisplay}</span>
|
||||
${method.description ? `<span class="shipping-method-desc">${method.description}</span>` : ''}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
methodsHtml += '</div>';
|
||||
|
||||
// Insert after country selector
|
||||
const countryRow = $('.formipay-shipping-row');
|
||||
const shippingRow = `
|
||||
<tr class="formipay-shipping-method-row">
|
||||
<td colspan="2">
|
||||
${methodsHtml}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
countryRow.after(shippingRow);
|
||||
|
||||
// Bind shipping method change events
|
||||
$('.formipay-shipping-method').on('change', (e) => {
|
||||
const target = $(e.currentTarget);
|
||||
const cost = parseFloat(target.data('cost'));
|
||||
const type = target.data('type');
|
||||
const baseCost = parseFloat(target.data('base-cost'));
|
||||
const method = target.val();
|
||||
|
||||
// For percentage, recalculate in case order total changed
|
||||
let finalCost = cost;
|
||||
if (type === 'percentage') {
|
||||
finalCost = (this.getOrderTotal() * baseCost) / 100;
|
||||
}
|
||||
|
||||
// Update hidden input
|
||||
$('.formipay-shipping-method-input').val(method);
|
||||
|
||||
// Update order total
|
||||
this.updateOrderTotal(finalCost);
|
||||
});
|
||||
|
||||
// Trigger change on default method to set initial cost
|
||||
$(`input[name="shipping_method_display"][value="${defaultMethod}"]`).trigger('change');
|
||||
},
|
||||
|
||||
hideShippingMethods() {
|
||||
$('.formipay-shipping-method-row').remove();
|
||||
},
|
||||
|
||||
getOrderTotal() {
|
||||
// Get current order total (excluding shipping)
|
||||
const subtotalRow = $('.formipay-total-row td').text();
|
||||
const subtotal = this.parseCurrency(subtotalRow);
|
||||
return subtotal;
|
||||
},
|
||||
|
||||
updateOrderTotal(shippingCost) {
|
||||
const currentTotal = this.getOrderTotal();
|
||||
const newTotal = currentTotal + shippingCost;
|
||||
|
||||
// Update the total display
|
||||
const totalRow = $('.formipay-grand-total-row td');
|
||||
totalRow.text(this.formatCost(newTotal));
|
||||
|
||||
// Update submit button
|
||||
const submitBtn = $('.formipay-submit-button');
|
||||
const currentText = submitBtn.attr('data-button-text');
|
||||
submitBtn.html(`${currentText} - ${this.formatCost(newTotal)}`);
|
||||
},
|
||||
|
||||
formatCost(cost, currency) {
|
||||
// Format cost using same format as product prices
|
||||
const formatted = cost.toFixed(formipay.decimal_digits || 2);
|
||||
return (currency || formipay.currency || '') + ' ' + formatted;
|
||||
},
|
||||
|
||||
parseCurrency(text) {
|
||||
// Parse currency string to get numeric value
|
||||
// Removes currency symbols and formats
|
||||
const numeric = text.replace(/[^\d.-]/g, '');
|
||||
return parseFloat(numeric) || 0;
|
||||
},
|
||||
|
||||
showError(message) {
|
||||
const errorHtml = `
|
||||
<div class="formipay-shipping-error notice notice-error">
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
`;
|
||||
$('.formipay-shipping-method-row').html(`<td colspan="2">${errorHtml}</td>`);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Initialize when document is ready
|
||||
$(document).ready(() => {
|
||||
if (typeof formipay_shipping !== 'undefined' && typeof formipay_shipping.labels !== 'undefined') {
|
||||
FormipayCheckoutShipping.init();
|
||||
}
|
||||
});
|
||||
|
||||
// Expose for global access
|
||||
window.FormipayCheckoutShipping = FormipayCheckoutShipping;
|
||||
|
||||
})(jQuery);
|
||||
1073
src/admin/components/field-renderer/FieldTypes/VariationField.js
Normal file
1073
src/admin/components/field-renderer/FieldTypes/VariationField.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user