array( 'label' => 'Order Confirmation', 'description' => 'Sent when a WooCommerce order is placed (Thank You page).', 'placeholders' => '{{1}} = Order ID, {{2}} = Amount', 'sample' => "Thank you for shopping with Admark 🙏\n\nYour order has been successfully placed.\n\n📦 Order ID: {{1}}\n💰 Amount: ₹{{2}}\n\nWe'll notify you once your order is dispatched.", 'var_count' => 2, 'var_defaults' => array( 1 => 'order_id', 2 => 'order_total' ), ), 'payment_confirmation' => array( 'label' => 'Payment Confirmation', 'description' => 'Sent when payment status changes to processing/completed.', 'placeholders' => '{{1}} = Customer Name, {{2}} = Order ID', 'sample' => "Hi {{1}}\n\nWe have received your payment successfully for Order ID {{2}} ✅\n\nThank you for choosing Admark.\n\nYour order is now being processed.", 'var_count' => 2, 'var_defaults' => array( 1 => 'customer_name', 2 => 'order_id' ), ), 'order_dispatch' => array( 'label' => 'Order Dispatch', 'description' => 'Sent when order status changes to Shipped.', 'placeholders' => '{{1}} = Customer Name, {{2}} = Order ID, {{3}} = Tracking Link', 'sample' => "Good news {{1}} 🎉\n\nYour Admark order has been dispatched.\n\n📦 Order ID: {{2}}\n🚚 Tracking Link: {{3}}\n\nThank you for shopping with us.", 'var_count' => 3, 'var_defaults' => array( 1 => 'customer_name', 2 => 'order_id', 3 => 'tracking_link' ), ), 'delivery_confirmation' => array( 'label' => 'Delivery Confirmation', 'description' => 'Sent when order status changes to Completed.', 'placeholders' => '{{1}} = Customer Name', 'sample' => "Hi {{1}}\n\nYour Admark order has been delivered successfully ✅\n\nWe hope you enjoy the products.\n\nThank you for shopping with Admark 🙏", 'var_count' => 1, 'var_defaults' => array( 1 => 'customer_name' ), ), 'feedback_request' => array( 'label' => 'Feedback / Review Request', 'description' => 'Sent 2 days after delivery via WP-Cron.', 'placeholders' => '{{1}} = Customer Name, {{2}} = Review Link', 'sample' => "Namaste {{1}}\n\nWe hope you loved your Admark purchase 😊\n\nPlease share your feedback and help us serve you better.\n\n⭐ Give Review:\n{{2}}\n\nThank you for being a valued customer.", 'var_count' => 2, 'var_defaults' => array( 1 => 'customer_name', 2 => 'review_link' ), ), 'reorder_reminder' => array( 'label' => 'Repeat Purchase / Reorder', 'description' => 'Sent manually or via campaign to encourage repeat orders.', 'placeholders' => '{{1}} = Customer Name, {{2}} = Shop Link', 'sample' => "Hello {{1}}\n\nRunning low on your favorite Admark products?\n\n🛒 Reorder quickly here:\n{{2}}\n\nEnjoy hassle-free shopping and doorstep delivery.", 'var_count' => 2, 'var_defaults' => array( 1 => 'customer_name', 2 => 'shop_url' ), ), 'offer_campaign' => array( 'label' => 'Offer & Festival Campaign', 'description' => 'Promotional or festival offer messages with customer targeting and scheduling.', 'placeholders' => '{{1}} = Shop/Offer Link', 'sample' => "🎉 Special Offer from Admark 🎉\n\nGet exciting deals on selected products for a limited time.\n\n🛍️ Shop Now:\n{{1}}\n\nHurry! Offer valid till stocks last.", 'var_count' => 1, 'var_defaults' => array( 1 => 'shop_url' ), ), 'welcome' => array( 'label' => 'Welcome Message', 'description' => 'Sent when a new customer registers on the website.', 'placeholders' => '{{1}} = Customer Name, {{2}} = Shop Link', 'sample' => "Namaste {{1}} 👋\n\nWelcome to Admark Online Shopping.\n\nExplore authentic Admark products, exclusive offers, and easy doorstep delivery.\n\n🛒 Shop Now: {{2}}\n\nIf you need any help regarding products or orders, feel free to reply to this message.", 'var_count' => 2, 'var_defaults' => array( 1 => 'customer_name', 2 => 'shop_url' ), ), 'add_to_cart' => array( 'label' => 'Add to Cart Reminder', 'description' => 'Sent ~20 minutes after a product is added to the cart (logged-in users).', 'placeholders' => '{{1}} = Customer Name, {{2}} = Cart Link', 'sample' => "Hi {{1}} 😊\n\nYou've added products to your cart on Admark Shopping.\n\n🛍️ Your selected items are waiting for you.\n\nComplete your order here:\n{{2}}\n\nOrder now before stock runs out.", 'var_count' => 2, 'var_defaults' => array( 1 => 'customer_name', 2 => 'cart_url' ), ), 'abandoned_cart' => array( 'label' => 'Abandoned Cart', 'description' => 'Sent 3 hours after cart is abandoned without purchase.', 'placeholders' => '{{1}} = Customer Name, {{2}} = Cart/Resume Link', 'sample' => "Hello {{1}}\n\nLooks like you left some items in your Admark cart 🛒\n\nComplete your purchase now and get your favorite products delivered to your doorstep.\n\n👉 Resume Order:\n{{2}}\n\nNeed any assistance? Simply reply to this message.", 'var_count' => 2, 'var_defaults' => array( 1 => 'customer_name', 2 => 'cart_url' ), ), 'payment_failed' => array( 'label' => 'Payment Failed', 'description' => 'Sent when payment status changes to Failed.', 'placeholders' => '{{1}} = Customer Name, {{2}} = Order ID', 'sample' => "Hi {{1}}\n\nWe were unable to process your payment for Order ID {{2}} ❌\n\nPlease try again or contact us for assistance.\n\nNeed help? Reply to this message.", 'var_count' => 2, 'var_defaults' => array( 1 => 'customer_name', 2 => 'order_id' ), ), 'refund' => array( 'label' => 'Refund', 'description' => 'Sent when order is refunded.', 'placeholders' => '{{1}} = Customer Name, {{2}} = Order ID, {{3}} = Refund Amount', 'sample' => "Hi {{1}}\n\nYour refund for Order ID {{2}} has been processed ✅\n\nRefund Amount: ₹{{3}}\n\nYour money has been returned to your original payment method.\n\nThank you for shopping with Admark.", 'var_count' => 3, 'var_defaults' => array( 1 => 'customer_name', 2 => 'order_id', 3 => 'order_total' ), ), ); } // Option getters (fallback to constants) function wats_get_api_key() { $opt = trim( (string) get_option( 'wats_api_key', '' ) ); return $opt !== '' ? $opt : WATS_API_KEY; } function wats_get_phone_number_id() { $opt = trim( (string) get_option( 'wats_phone_number_id', '' ) ); return $opt !== '' ? $opt : WATS_PHONE_NUMBER_ID; } function wats_get_selected_template() { return trim( (string) get_option( 'wats_selected_template', '' ) ); } function wats_get_event_template( $event ) { return trim( (string) get_option( 'wats_tpl_' . $event, '' ) ); } function wats_get_event_enabled( $event ) { return (bool) get_option( 'wats_enable_' . $event, 0 ); } /** * Get configured delay in seconds for timed events (welcome, add_to_cart, abandoned_cart, feedback_request). * Defaults: welcome=0s, add_to_cart=20m, abandoned_cart=3h, feedback_request=2d. */ function wats_get_event_delay( $event ) { $defaults = array( 'welcome' => array( 'value' => 0, 'unit' => 'minutes' ), 'add_to_cart' => array( 'value' => 20, 'unit' => 'minutes' ), 'abandoned_cart' => array( 'value' => 3, 'unit' => 'hours' ), 'feedback_request' => array( 'value' => 2, 'unit' => 'days' ), ); $def = isset( $defaults[ $event ] ) ? $defaults[ $event ] : array( 'value' => 0, 'unit' => 'minutes' ); $val = (int) get_option( 'wats_delay_val_' . $event, $def['value'] ); $unit = sanitize_key( (string) get_option( 'wats_delay_unit_' . $event, $def['unit'] ) ); switch ( $unit ) { case 'hours': return $val * HOUR_IN_SECONDS; case 'days': return $val * DAY_IN_SECONDS; default: return $val * MINUTE_IN_SECONDS; } } // ==== VARIABLE MAPPING HELPERS ==== /** * Returns all available data fields that can be mapped to template variables. */ function wats_get_variable_options() { return array( 'customer_name' => 'Customer Name', 'phone' => 'Mobile Number', 'email' => 'Email', 'billing_address' => 'Billing Address', 'order_id' => 'Order ID', 'order_total' => 'Order Amount', 'product_names' => 'Product Name(s)', 'product_qty' => 'Product Quantity', 'tracking_link' => 'Tracking Link', 'shop_url' => 'Shop URL', 'cart_url' => 'Cart URL', 'review_link' => 'Review Link', ); } /** * Get the saved variable mapping for an event slot, falling back to the event default. */ function wats_get_var_map( $event, $slot ) { $saved = trim( (string) get_option( 'wats_varmap_' . $event . '_' . $slot, '' ) ); if ( $saved !== '' ) { return $saved; } $events = wats_get_events(); return isset( $events[ $event ]['var_defaults'][ $slot ] ) ? $events[ $event ]['var_defaults'][ $slot ] : ''; } /** * Resolve a variable map key to an actual value using order/user context. */ function wats_resolve_var( $key, $order = null, $user = null ) { switch ( $key ) { case 'customer_name': if ( $order ) { return trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ); } if ( $user ) { return trim( $user->first_name . ' ' . $user->last_name ) ?: $user->display_name; } return ''; case 'phone': if ( $order ) { return $order->get_billing_phone(); } if ( $user ) { return (string) get_user_meta( $user->ID, 'billing_phone', true ); } return ''; case 'email': if ( $order ) { return $order->get_billing_email(); } if ( $user ) { return $user->user_email; } return ''; case 'billing_address': if ( $order ) { return wp_strip_all_tags( $order->get_formatted_billing_address() ); } return ''; case 'order_id': if ( $order ) { return (string) $order->get_id(); } return ''; case 'order_total': if ( $order ) { return html_entity_decode( wp_strip_all_tags( $order->get_formatted_order_total() ) ); } return ''; case 'product_names': if ( $order ) { $names = array(); foreach ( $order->get_items() as $item ) { $names[] = $item->get_name(); } return implode( ', ', $names ); } return ''; case 'product_qty': if ( $order ) { $qtys = array(); foreach ( $order->get_items() as $item ) { $qtys[] = (string) $item->get_quantity(); } return implode( ', ', $qtys ); } return ''; case 'tracking_link': if ( $order ) { $link = (string) $order->get_meta( '_wats_tracking_link' ); return $link !== '' ? $link : home_url(); } return home_url(); case 'shop_url': return function_exists( 'wc_get_page_id' ) ? ( get_permalink( wc_get_page_id( 'shop' ) ) ?: home_url( '/shop/' ) ) : home_url( '/shop/' ); case 'cart_url': return function_exists( 'wc_get_cart_url' ) ? wc_get_cart_url() : home_url( '/cart/' ); case 'review_link': if ( $order ) { return get_permalink( $order->get_id() ) ?: home_url(); } return home_url(); default: return ''; } } // ==== SANITIZERS ==== function wats_sanitize_api_key( $value ) { return sanitize_text_field( (string) $value ); } function wats_sanitize_phone_number_id( $value ) { $value = sanitize_text_field( (string) $value ); // keep digits only return preg_replace( '/\D+/', '', $value ); } function wats_sanitize_selected_template( $value ) { $val = sanitize_text_field( (string) $value ); if ( $val === '' ) { return ''; } $api_key_present = ( wats_get_api_key() !== '' ); $templates = $api_key_present ? get_transient( 'wats_templates_cache' ) : array(); if ( $templates === false && $api_key_present ) { // Best-effort fetch when saving, so we can validate $templates = wats_fetch_templates(); } if ( ! is_array( $templates ) ) { $templates = array(); } return in_array( $val, $templates, true ) ? $val : ''; } function wats_sanitize_send_count( $value ) { return absint( $value ); } function wats_sanitize_send_log( $value ) { if ( ! is_array( $value ) ) { return array(); } $out = array(); $tz = wp_timezone(); foreach ( $value as $entry ) { if ( is_array( $entry ) ) { $utc = isset( $entry['utc'] ) ? (int) $entry['utc'] : 0; $local = isset( $entry['local'] ) ? sanitize_text_field( (string) $entry['local'] ) : ''; if ( $utc > 0 && $utc < 32503680000 ) { // simple sanity bound (< year 3000) $out[] = array( 'local' => $local, 'utc' => $utc ); } } elseif ( is_string( $entry ) ) { $dt = date_create_immutable( $entry, $tz ); if ( $dt ) { $out[] = array( 'local' => sanitize_text_field( $entry ), 'utc' => $dt->getTimestamp() - (int) $tz->getOffset( $dt ), ); } } } // keep the log from growing unbounded when saved via options if ( count( $out ) > 5000 ) { $out = array_slice( $out, -5000 ); } return $out; } function wats_sanitize_campaign_customers( $value ) { if ( ! is_array( $value ) ) { return array(); } $out = array(); foreach ( $value as $customer ) { if ( is_array( $customer ) && isset( $customer['id'] ) ) { $out[] = array( 'id' => absint( $customer['id'] ), 'email' => sanitize_email( $customer['email'] ?? '' ) ); } } return $out; } function wats_sanitize_campaign_schedule( $value ) { $val = sanitize_text_field( (string) $value ); if ( $val === '' ) { return ''; } // Validate schedule format (YYYY-MM-DD HH:MM) if ( preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $val ) ) { return $val; } return ''; } // ===================== /** * Normalize an Indian phone number to 91XXXXXXXXXX (digits only). */ function wats_normalize_phone( $raw ) { $digits = preg_replace( '/\D+/', '', (string) $raw ); if ( strlen( $digits ) === 11 && wats_str_starts_with( $digits, '0' ) ) { $digits = substr( $digits, 1 ); } if ( wats_str_starts_with( $digits, '91' ) && strlen( $digits ) === 12 ) { return $digits; } if ( strlen( $digits ) === 10 ) { return '91' . $digits; } return $digits; } /** * Prepare values for {{1}}..{{5}} and return csvVariables as a URL-ready CSV string. */ function wats_build_csv_variables( $order ) { $items = $order->get_items(); $names = array(); $quantities = array(); foreach ( $items as $item ) { $names[] = $item->get_name(); $quantities[] = (string) $item->get_quantity(); } $products_str = implode( ', ', $names ); $quantities_str = implode( ', ', $quantities ); $total_str = html_entity_decode( wp_strip_all_tags( $order->get_formatted_order_total() ) ); $first_name = $order->get_billing_first_name(); $last_name = $order->get_billing_last_name(); $customer_name = trim( $first_name . ' ' . $last_name ); $values = array( $customer_name, // {{1}} (string) $order->get_id(), // {{2}} $products_str, // {{3}} $quantities_str, // {{4}} $total_str, // {{5}} ); $encoded = array_map( 'rawurlencode', $values ); return implode( ',', $encoded ); } /** * Build csvVariables string for a specific automation event using saved variable mappings. * Uses the saved wats_varcount_{event} option (set by the admin UI) so any template * variable count is respected, not just the hardcoded event default. */ function wats_build_csv_for_event( $event, $order = null, $user = null ) { $var_count = (int) get_option( 'wats_varcount_' . $event, 0 ); if ( $var_count <= 0 ) { // Fall back to event definition default $events = wats_get_events(); $var_count = isset( $events[ $event ]['var_count'] ) ? (int) $events[ $event ]['var_count'] : 2; } $vars = array(); for ( $i = 1; $i <= $var_count; $i++ ) { $map_key = wats_get_var_map( $event, $i ); $vars[] = $map_key !== '' ? wats_resolve_var( $map_key, $order, $user ) : ''; } return implode( ',', array_map( 'rawurlencode', $vars ) ); } /** * Build API URL with query params, using csvVariables. */ function wats_build_url( $args ) { $params = array( 'api-key' => wats_get_api_key(), 'templateName' => $args['templateName'] ?? '', 'phoneNumber' => $args['phoneNumber'] ?? '', 'phoneNumberId' => wats_get_phone_number_id(), 'csvVariables' => $args['csvVariables'] ?? '', ); $params = array_filter( $params, function( $v ) { return $v !== '' && $v !== null; } ); return add_query_arg( $params, WATS_API_BASE ); } /** * Send Admark CRM template call for a given event and log timestamp for stats. */ function wats_send_template( $order, $event = 'order_confirmation' ) { try { if ( ! class_exists( 'WC_Order' ) || ! ( is_object( $order ) && $order instanceof WC_Order ) ) { return; } // Resolve template for this event, falling back to legacy default $template = wats_get_event_template( $event ); if ( $template === '' && $event === 'order_confirmation' ) { $template = wats_get_selected_template(); } $order_id = $order->get_id(); $raw_phone = $order->get_billing_phone(); if ( empty( $raw_phone ) ) { wc_get_logger()->warning( "[WATS] Order {$order_id} / {$event}: Missing billing phone; skipping." ); return; } if ( $template === '' ) { wc_get_logger()->warning( "[WATS] Order {$order_id} / {$event}: No template configured; skipping." ); return; } $phone = wats_normalize_phone( $raw_phone ); $csv = wats_build_csv_for_event( $event, $order ); $url = wats_build_url( array( 'templateName' => $template, 'phoneNumber' => $phone, 'csvVariables' => $csv, ) ); $response = wp_remote_get( $url, array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/json' ), ) ); if ( is_wp_error( $response ) ) { wc_get_logger()->error( "[WATS] Order {$order_id} / {$event}: HTTP error - " . $response->get_error_message() ); return; } $code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); if ( $code >= 200 && $code < 300 ) { wc_get_logger()->info( "[WATS] Order {$order_id} / {$event}: WhatsApp sent to {$phone}. Response: {$body}" ); $count = (int) get_option( 'wats_send_count', 0 ); update_option( 'wats_send_count', $count + 1, false ); $log = get_option( 'wats_send_log', array() ); if ( ! is_array( $log ) ) { $log = array(); } $log[] = array( 'local' => current_time( 'mysql' ), 'utc' => time(), 'event' => $event ); update_option( 'wats_send_log', $log, false ); } else { wc_get_logger()->error( "[WATS] Order {$order_id} / {$event}: API returned {$code}. Response: {$body}" ); } } catch ( Throwable $e ) { wc_get_logger()->error( "[WATS] Event {$event} error: " . $e->getMessage() ); } } // ==== AUTOMATION HOOKS ==== // 1. Order Confirmation — Thank You page add_action( 'woocommerce_thankyou', function( $order_id ) { if ( ! $order_id ) { return; } if ( ! wats_get_event_enabled( 'order_confirmation' ) ) { return; } $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_order_confirmation_sent' ) ) { wats_send_template( $order, 'order_confirmation' ); $order->update_meta_data( '_wats_order_confirmation_sent', current_time( 'mysql' ) ); $order->save(); } }, 20, 1 ); // 2. Payment Confirmation — status → processing add_action( 'woocommerce_order_status_processing', function( $order_id ) { if ( ! wats_get_event_enabled( 'payment_confirmation' ) ) { return; } $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_payment_confirmation_sent' ) ) { wats_send_template( $order, 'payment_confirmation' ); $order->update_meta_data( '_wats_payment_confirmation_sent', current_time( 'mysql' ) ); $order->save(); } }, 10, 1 ); // 3. Order Dispatch — Fix: Fall back to 'completed' or capture 'shipped' if it exists add_action( 'woocommerce_order_status_shipped', 'wats_handle_order_dispatch', 10, 1 ); add_action( 'woocommerce_order_status_completed', 'wats_handle_order_dispatch', 5, 1 ); // Fallback trigger function wats_handle_order_dispatch( $order_id ) { if ( ! wats_get_event_enabled( 'order_dispatch' ) ) { return; } $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_dispatch_sent' ) ) { wats_send_template( $order, 'order_dispatch' ); $order->update_meta_data( '_wats_dispatch_sent', current_time( 'mysql' ) ); $order->save(); } } // 4. Delivery Confirmation — status → completed add_action( 'woocommerce_order_status_completed', function( $order_id ) { if ( ! wats_get_event_enabled( 'delivery_confirmation' ) ) { return; } $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_delivery_sent' ) ) { wats_send_template( $order, 'delivery_confirmation' ); $order->update_meta_data( '_wats_delivery_sent', current_time( 'mysql' ) ); $order->save(); } }, 10, 1 ); // 5. Feedback Request — 2 days after delivery (WP-Cron) add_action( 'woocommerce_order_status_completed', function( $order_id ) { if ( ! wats_get_event_enabled( 'feedback_request' ) ) { return; } if ( ! wp_next_scheduled( 'wats_send_feedback', array( $order_id ) ) ) { wp_schedule_single_event( time() + 2 * DAY_IN_SECONDS, 'wats_send_feedback', array( $order_id ) ); } }, 20, 1 ); add_action( 'wats_send_feedback', function( $order_id ) { $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_feedback_sent' ) ) { wats_send_template( $order, 'feedback_request' ); $order->update_meta_data( '_wats_feedback_sent', current_time( 'mysql' ) ); $order->save(); } }, 10, 1 ); // 6. Payment Failed — Fixed Trigger Hook add_action( 'woocommerce_order_status_failed', function( $order_id ) { if ( ! wats_get_event_enabled( 'payment_failed' ) ) { return; } $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_payment_failed_sent' ) ) { wats_send_template( $order, 'payment_failed' ); $order->update_meta_data( '_wats_payment_failed_sent', current_time( 'mysql' ) ); $order->save(); } }, 10, 1 ); // 7. Refund — Fixed Trigger Hook (Captures built-in WooCommerce refunds natively) add_action( 'woocommerce_order_refunded', function( $order_id, $refund_id ) { if ( ! wats_get_event_enabled( 'refund' ) ) { return; } $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_refund_sent_' . $refund_id ) ) { wats_send_template( $order, 'refund' ); $order->update_meta_data( '_wats_refund_sent_' . $refund_id, current_time( 'mysql' ) ); $order->save(); } }, 10, 2 ); // 8. Welcome Message — new user registration (optionally delayed) add_action( 'user_register', function( $user_id ) { if ( ! wats_get_event_enabled( 'welcome' ) ) { return; } $template = wats_get_event_template( 'welcome' ); if ( $template === '' ) { return; } $delay = wats_get_event_delay( 'welcome' ); if ( $delay > 0 ) { wp_schedule_single_event( time() + $delay, 'wats_send_welcome', array( $user_id ) ); } else { $user = get_userdata( $user_id ); $phone = get_user_meta( $user_id, 'billing_phone', true ); if ( empty( $phone ) ) { return; } $csv = wats_build_csv_for_event( 'welcome', null, $user ); wp_remote_get( wats_build_url( array( 'templateName' => $template, 'phoneNumber' => wats_normalize_phone( $phone ), 'csvVariables' => $csv ) ), array( 'timeout' => 15 ) ); } }, 10, 1 ); add_action( 'wats_send_welcome', function( $user_id ) { $template = wats_get_event_template( 'welcome' ); if ( $template === '' ) { return; } $user = get_userdata( $user_id ); $phone = get_user_meta( $user_id, 'billing_phone', true ); if ( empty( $phone ) ) { return; } $csv = wats_build_csv_for_event( 'welcome', null, $user ); wp_remote_get( wats_build_url( array( 'templateName' => $template, 'phoneNumber' => wats_normalize_phone( $phone ), 'csvVariables' => $csv ) ), array( 'timeout' => 15 ) ); }, 10, 1 ); // 9. Add to Cart Reminder — configurable delay after add to cart (logged-in users) add_action( 'woocommerce_add_to_cart', function() { if ( ! wats_get_event_enabled( 'add_to_cart' ) ) { return; } $user_id = get_current_user_id(); if ( ! $user_id ) { return; } $args = array( $user_id ); $existing = wp_next_scheduled( 'wats_send_cart_reminder', $args ); if ( $existing ) { wp_unschedule_event( $existing, 'wats_send_cart_reminder', $args ); } $delay = wats_get_event_delay( 'add_to_cart' ); wp_schedule_single_event( time() + max( 60, $delay ), 'wats_send_cart_reminder', $args ); }, 10, 0 ); add_action( 'wats_send_cart_reminder', function( $user_id ) { $template = wats_get_event_template( 'add_to_cart' ); if ( $template === '' ) { return; } $user = get_userdata( $user_id ); $phone = get_user_meta( $user_id, 'billing_phone', true ); if ( empty( $phone ) ) { return; } $csv = wats_build_csv_for_event( 'add_to_cart', null, $user ); wp_remote_get( wats_build_url( array( 'templateName' => $template, 'phoneNumber' => wats_normalize_phone( $phone ), 'csvVariables' => $csv ) ), array( 'timeout' => 15 ) ); }, 10, 1 ); // 10. Abandoned Cart — configurable delay after add_to_cart, cancelled if order placed add_action( 'woocommerce_add_to_cart', function() { if ( ! wats_get_event_enabled( 'abandoned_cart' ) ) { return; } $user_id = get_current_user_id(); if ( ! $user_id ) { return; } $args = array( $user_id ); $existing = wp_next_scheduled( 'wats_send_abandoned_cart', $args ); if ( $existing ) { wp_unschedule_event( $existing, 'wats_send_abandoned_cart', $args ); } $delay = wats_get_event_delay( 'abandoned_cart' ); wp_schedule_single_event( time() + max( 60, $delay ), 'wats_send_abandoned_cart', $args ); }, 10, 0 ); add_action( 'woocommerce_thankyou', function( $order_id ) { $order = wc_get_order( $order_id ); $user_id = $order ? $order->get_user_id() : 0; if ( $user_id ) { foreach ( array( 'wats_send_abandoned_cart', 'wats_send_cart_reminder' ) as $hook ) { $args = array( $user_id ); $existing = wp_next_scheduled( $hook, $args ); if ( $existing ) { wp_unschedule_event( $existing, $hook, $args ); } } } }, 5, 1 ); add_action( 'wats_send_abandoned_cart', function( $user_id ) { $template = wats_get_event_template( 'abandoned_cart' ); if ( $template === '' ) { return; } $user = get_userdata( $user_id ); $phone = get_user_meta( $user_id, 'billing_phone', true ); if ( empty( $phone ) ) { return; } $csv = wats_build_csv_for_event( 'abandoned_cart', null, $user ); wp_remote_get( wats_build_url( array( 'templateName' => $template, 'phoneNumber' => wats_normalize_phone( $phone ), 'csvVariables' => $csv ) ), array( 'timeout' => 15 ) ); }, 10, 1 ); // ==== SURECART INTEGRATION ==== /** * Safely convert any SureCart model object to a plain associative array. * SureCart uses magic __get() so isset()/empty() fail — JSON round-trip bypasses this. */ function wats_sc_to_array( $obj ) { if ( is_array( $obj ) ) { return $obj; } try { $json = wp_json_encode( $obj ); if ( $json ) { $arr = json_decode( $json, true ); return is_array( $arr ) ? $arr : array(); } } catch ( Throwable $e ) {} return (array) $obj; } /** * Read a dot-path from a nested array: wats_sc_get(['a','b','c'], $data) returns $data['a']['b']['c']. */ function wats_sc_get( $keys, $data ) { foreach ( $keys as $k ) { if ( ! is_array( $data ) || ! array_key_exists( $k, $data ) ) { return null; } $data = $data[ $k ]; } return ( $data !== null && $data !== '' ) ? $data : null; } /** * Extract phone from a SureCart object (checkout session OR order). * Uses JSON round-trip to bypass magic __get(), then falls back to WP user meta. */ function wats_get_surecart_phone( $obj ) { $d = wats_sc_to_array( $obj ); // All known paths where SureCart stores a real phone field $paths = array( array( 'billing_address', 'phone' ), array( 'shipping_address', 'phone' ), array( 'customer', 'phone' ), array( 'phone' ), array( 'inherited_phone' ), array( 'billing_phone' ), array( 'customer_phone' ), array( 'metadata', 'phone' ), array( 'metadata', 'billing_phone' ), array( 'order', 'billing_address', 'phone' ), array( 'order', 'shipping_address', 'phone' ), array( 'order', 'customer', 'phone' ), ); foreach ( $paths as $path ) { $v = wats_sc_get( $path, $d ); if ( $v !== null && $v !== '' && $v !== false ) { return (string) $v; } } // SureCart has no built-in phone field — users often type their phone in first_name / name. // Detect: value is digits-only (with optional +, spaces, dashes) and 10–15 chars. foreach ( array( array( 'first_name' ), array( 'name' ), array( 'inherited_name' ) ) as $path ) { $v = wats_sc_get( $path, $d ); if ( $v !== null && $v !== '' ) { $digits = preg_replace( '/[^\d]/', '', (string) $v ); if ( strlen( $digits ) >= 10 && strlen( $digits ) <= 15 ) { return $digits; // treat as phone number } } } // WP user meta fallback — look up billing_phone by email $email_paths = array( array( 'email' ), array( 'inherited_email' ), array( 'customer', 'email' ), array( 'customer_email' ), array( 'order', 'customer', 'email' ), ); $email = ''; foreach ( $email_paths as $ep ) { $v = wats_sc_get( $ep, $d ); if ( $v ) { $email = (string) $v; break; } } if ( $email !== '' ) { $user = get_user_by( 'email', $email ); if ( $user ) { $phone = (string) get_user_meta( $user->ID, 'billing_phone', true ); if ( $phone !== '' ) { return $phone; } } } return ''; } /** * Resolve a mapping key against a SureCart object (checkout session or order). * Uses JSON round-trip to safely access nested properties. */ function wats_resolve_surecart_var( $key, $obj ) { $d = wats_sc_to_array( $obj ); $order = isset( $d['order'] ) && is_array( $d['order'] ) ? $d['order'] : $d; switch ( $key ) { case 'customer_name': // customer.name is the reliable source (e.g. "Vinit Marathe") // first_name is unreliable — users sometimes enter phone there $v = wats_sc_get( array( 'customer', 'name' ), $d ) ?? wats_sc_get( array( 'customer_name' ), $d ) ?? wats_sc_get( array( 'customer', 'name' ), $order ); if ( ! $v ) { // Only use first_name if it does NOT look like a phone number $fn = wats_sc_get( array( 'first_name' ), $d ); if ( $fn ) { $digits = preg_replace( '/[^\d]/', '', (string) $fn ); if ( strlen( $digits ) < 10 ) { $v = $fn; } } } return $v ? (string) $v : ''; case 'phone': return wats_get_surecart_phone( $obj ); case 'email': $v = wats_sc_get( array( 'customer', 'email' ), $d ) ?? wats_sc_get( array( 'customer_email' ), $d ) ?? wats_sc_get( array( 'email' ), $d ); return $v ? (string) $v : ''; case 'order_id': $num = wats_sc_get( array( 'order', 'number' ), $d ) ?? wats_sc_get( array( 'number' ), $d ); if ( $num ) { return '#' . $num; } $id = wats_sc_get( array( 'order', 'id' ), $d ) ?? wats_sc_get( array( 'id' ), $d ); return $id ? (string) $id : ''; case 'order_total': $amount = wats_sc_get( array( 'order', 'total_amount' ), $d ) ?? wats_sc_get( array( 'total_amount' ), $d ) ?? wats_sc_get( array( 'order', 'amount_due' ), $d ) ?? wats_sc_get( array( 'amount_due' ), $d ); if ( $amount !== null ) { $currency = wats_sc_get( array( 'currency' ), $d ) ?? wats_sc_get( array( 'order', 'currency' ), $d ) ?? 'INR'; return strtoupper( $currency ) . ' ' . number_format( (int) $amount / 100, 2 ); } return ''; case 'product_names': // Try multiple paths for line items foreach ( array( $d, $order ) as $src ) { $items = wats_sc_get( array( 'line_items', 'data' ), $src ) ?? wats_sc_get( array( 'line_items' ), $src ); if ( is_array( $items ) ) { $names = array(); foreach ( $items as $item ) { $name = wats_sc_get( array( 'price', 'product', 'name' ), $item ) ?? wats_sc_get( array( 'product_name' ), $item ) ?? wats_sc_get( array( 'name' ), $item ); if ( $name ) { $names[] = $name; } } if ( $names ) { return implode( ', ', $names ); } } } return ''; case 'product_qty': foreach ( array( $d, $order ) as $src ) { $items = wats_sc_get( array( 'line_items', 'data' ), $src ) ?? wats_sc_get( array( 'line_items' ), $src ); if ( is_array( $items ) ) { $qtys = array(); foreach ( $items as $item ) { $qtys[] = (string) ( wats_sc_get( array( 'quantity' ), $item ) ?? 1 ); } if ( $qtys ) { return implode( ', ', $qtys ); } } } return ''; case 'billing_address': $addr = wats_sc_get( array( 'billing_address' ), $d ) ?? wats_sc_get( array( 'shipping_address' ), $d ) ?? wats_sc_get( array( 'billing_address' ), $order ) ?? wats_sc_get( array( 'shipping_address' ), $order ); if ( is_array( $addr ) ) { $parts = array_filter( array( $addr['line_1'] ?? '', $addr['line_2'] ?? '', $addr['city'] ?? '', $addr['state'] ?? '', $addr['postal_code'] ?? '', $addr['country'] ?? '', ) ); return implode( ', ', $parts ); } return ''; case 'shop_url': return home_url(); case 'cart_url': return home_url(); case 'tracking_link': return home_url(); case 'review_link': return home_url(); default: return ''; } } /** * Build csvVariables string for a SureCart event using saved variable mappings. */ function wats_build_csv_for_surecart_event( $event, $order ) { $var_count = (int) get_option( 'wats_varcount_' . $event, 0 ); if ( $var_count <= 0 ) { $events = wats_get_events(); $var_count = isset( $events[ $event ]['var_count'] ) ? (int) $events[ $event ]['var_count'] : 2; } $vars = array(); for ( $i = 1; $i <= $var_count; $i++ ) { $map_key = wats_get_var_map( $event, $i ); $vars[] = $map_key !== '' ? wats_resolve_surecart_var( $map_key, $order ) : ''; } return implode( ',', array_map( 'rawurlencode', $vars ) ); } /** * Send a WhatsApp template message for a SureCart event. * $sc_obj can be a checkout session OR an order — phone extraction handles both. */ function wats_send_template_surecart( $sc_obj, $event = 'order_confirmation' ) { try { $template = wats_get_event_template( $event ); if ( $template === '' ) { return; } $raw_phone = wats_get_surecart_phone( $sc_obj ); if ( empty( $raw_phone ) ) { return; } $phone = wats_normalize_phone( $raw_phone ); $csv = wats_build_csv_for_surecart_event( $event, $sc_obj ); $url = wats_build_url( array( 'templateName' => $template, 'phoneNumber' => $phone, 'csvVariables' => $csv, ) ); $response = wp_remote_get( $url, array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/json' ), ) ); if ( is_wp_error( $response ) ) { return; } $code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); if ( $code >= 200 && $code < 300 ) { $count = (int) get_option( 'wats_send_count', 0 ); update_option( 'wats_send_count', $count + 1, false ); $log = get_option( 'wats_send_log', array() ); if ( ! is_array( $log ) ) { $log = array(); } $log[] = array( 'local' => current_time( 'mysql' ), 'utc' => time(), 'event' => $event . '_sc' ); update_option( 'wats_send_log', $log, false ); } } catch ( Throwable $e ) { error_log( '[WATS SureCart] Event ' . $event . ' error: ' . $e->getMessage() ); } } /** * Central handler — accepts a checkout session or order object, deduplicates by order ID. */ function wats_handle_surecart_paid( $sc_obj ) { $d = wats_sc_to_array( $sc_obj ); $order_d = isset( $d['order'] ) && is_array( $d['order'] ) ? $d['order'] : $d; $order_id = (string) ( $order_d['id'] ?? '' ); if ( wats_get_event_enabled( 'order_confirmation' ) ) { if ( ! ( $order_id && get_option( '_wats_sc_order_confirmation_' . $order_id ) ) ) { wats_send_template_surecart( $sc_obj, 'order_confirmation' ); if ( $order_id ) { update_option( '_wats_sc_order_confirmation_' . $order_id, 1, false ); } } } if ( wats_get_event_enabled( 'payment_confirmation' ) ) { if ( ! ( $order_id && get_option( '_wats_sc_payment_confirmation_' . $order_id ) ) ) { wats_send_template_surecart( $sc_obj, 'payment_confirmation' ); if ( $order_id ) { update_option( '_wats_sc_payment_confirmation_' . $order_id, 1, false ); } } } } // Hook 1: surecart/checkout_session_completed — fires on every paid checkout add_action( 'surecart/checkout_session_completed', function( $checkout ) { wats_handle_surecart_paid( $checkout ); }, 10, 1 ); // Hook 2: surecart/order_paid — fires in some SureCart versions add_action( 'surecart/order_paid', function( $order ) { wats_handle_surecart_paid( $order ); }, 10, 1 ); // Hook 3: surecart/checkout_confirmed — alternate hook name in some versions add_action( 'surecart/checkout_confirmed', function( $checkout ) { wats_handle_surecart_paid( $checkout ); }, 10, 1 ); // Hook 4: SureCart Order Dispatch — fires when order is fulfilled add_action( 'surecart/order_fulfilled', function( $order ) { if ( ! wats_get_event_enabled( 'order_dispatch' ) ) { return; } $d = wats_sc_to_array( $order ); $order_id = (string) ( $d['id'] ?? '' ); if ( $order_id && get_option( '_wats_sc_dispatch_' . $order_id ) ) { return; } wats_send_template_surecart( $order, 'order_dispatch' ); if ( $order_id ) { update_option( '_wats_sc_dispatch_' . $order_id, 1, false ); } }, 10, 1 ); // Hook 5: SureCart Delivery Confirmation — also on fulfilled (same event, different template) add_action( 'surecart/order_fulfilled', function( $order ) { if ( ! wats_get_event_enabled( 'delivery_confirmation' ) ) { return; } $d = wats_sc_to_array( $order ); $order_id = (string) ( $d['id'] ?? '' ); if ( $order_id && get_option( '_wats_sc_delivery_' . $order_id ) ) { return; } wats_send_template_surecart( $order, 'delivery_confirmation' ); if ( $order_id ) { update_option( '_wats_sc_delivery_' . $order_id, 1, false ); } }, 20, 1 ); // 8. Campaign Event — manual trigger for offer & festival campaigns add_action( 'wats_send_campaign', function() { if ( ! wats_get_event_enabled( 'offer_campaign' ) ) { return; } $campaign_enabled = (bool) get_option( 'wats_campaign_enabled', 0 ); if ( ! $campaign_enabled ) { return; } $template = get_option( 'wats_campaign_template', '' ); if ( $template === '' ) { return; } $customers = get_option( 'wats_campaign_customers', array() ); if ( empty( $customers ) ) { return; } $schedule = get_option( 'wats_campaign_schedule', '' ); $now = current_time( 'mysql' ); if ( $schedule !== '' && $schedule > $now ) { return; } foreach ( $customers as $customer ) { $user = get_userdata( $customer['id'] ); if ( ! $user ) { continue; } $phone = get_user_meta( $user->ID, 'billing_phone', true ); if ( empty( $phone ) ) { continue; } $csv = wats_build_csv_for_event( 'offer_campaign', null, $user ); $url = wats_build_url( array( 'templateName' => $template, 'phoneNumber' => wats_normalize_phone( $phone ), 'csvVariables' => $csv, ) ); $response = wp_remote_get( $url, array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/json' ), ) ); if ( ! is_wp_error( $response ) ) { $code = wp_remote_retrieve_response_code( $response ); if ( $code >= 200 && $code < 300 ) { $count = (int) get_option( 'wats_send_count', 0 ); update_option( 'wats_send_count', $count + 1, false ); $log = get_option( 'wats_send_log', array() ); if ( ! is_array( $log ) ) { $log = array(); } $log[] = array( 'local' => current_time( 'mysql' ), 'utc' => time(), 'event' => 'offer_campaign' ); update_option( 'wats_send_log', $log, false ); } } } }, 10, 0 ); // 6. Payment Failed — status → failed add_action( 'woocommerce_order_status_failed', function( $order_id ) { if ( ! wats_get_event_enabled( 'payment_failed' ) ) { return; } $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_payment_failed_sent' ) ) { wats_send_template( $order, 'payment_failed' ); $order->update_meta_data( '_wats_payment_failed_sent', current_time( 'mysql' ) ); $order->save(); } }, 10, 1 ); // 7. Refund — status → refunded add_action( 'woocommerce_order_status_refunded', function( $order_id ) { if ( ! wats_get_event_enabled( 'refund' ) ) { return; } $order = wc_get_order( $order_id ); if ( $order && ! $order->get_meta( '_wats_refund_sent' ) ) { wats_send_template( $order, 'refund' ); $order->update_meta_data( '_wats_refund_sent', current_time( 'mysql' ) ); $order->save(); } }, 10, 1 ); // Hook 6: SureCart Feedback Request — scheduled after fulfillment add_action( 'surecart/order_fulfilled', function( $order ) { if ( ! wats_get_event_enabled( 'feedback_request' ) ) { return; } $d = wats_sc_to_array( $order ); $order_id = (string) ( $d['id'] ?? '' ); if ( ! $order_id ) { return; } $delay = wats_get_event_delay( 'feedback_request' ); if ( ! wp_next_scheduled( 'wats_sc_send_feedback', array( $order_id ) ) ) { wp_schedule_single_event( time() + max( 60, $delay ), 'wats_sc_send_feedback', array( $order_id ) ); } // Store order data for cron to use (stripped to essentials) update_option( '_wats_sc_feedback_data_' . $order_id, substr( wp_json_encode( $d ), 0, 8000 ), false ); }, 30, 1 ); add_action( 'wats_sc_send_feedback', function( $order_id ) { if ( ! wats_get_event_enabled( 'feedback_request' ) ) { return; } if ( get_option( '_wats_sc_feedback_sent_' . $order_id ) ) { return; } $raw = get_option( '_wats_sc_feedback_data_' . $order_id, '' ); $data = $raw ? json_decode( $raw, true ) : array(); if ( empty( $data ) ) { return; } // Reconstruct a simple stdClass for phone/var resolution $obj = (object) $data; wats_send_template_surecart( $obj, 'feedback_request' ); update_option( '_wats_sc_feedback_sent_' . $order_id, 1, false ); delete_option( '_wats_sc_feedback_data_' . $order_id ); }, 10, 1 ); // Hook 7: SureCart Welcome — fires when a new SureCart customer is created add_action( 'surecart/customer_created', function( $customer ) { if ( ! wats_get_event_enabled( 'welcome' ) ) { return; } $template = wats_get_event_template( 'welcome' ); if ( $template === '' ) { return; } $d = wats_sc_to_array( $customer ); $phone = wats_get_surecart_phone( $customer ); if ( empty( $phone ) ) { // Fallback: WP user meta via email $email = (string) ( $d['email'] ?? '' ); if ( $email !== '' ) { $user = get_user_by( 'email', $email ); if ( $user ) { $phone = (string) get_user_meta( $user->ID, 'billing_phone', true ); } } } if ( empty( $phone ) ) { return; } $delay = wats_get_event_delay( 'welcome' ); $name = (string) ( $d['name'] ?? '' ); $csv = wats_build_csv_for_surecart_event( 'welcome', $customer ); if ( $delay > 0 ) { update_option( '_wats_sc_welcome_data_' . preg_replace( '/[^a-z0-9]/', '', $phone ), json_encode( array( 'phone' => $phone, 'csv' => $csv, 'template' => $template ) ), false ); wp_schedule_single_event( time() + $delay, 'wats_sc_send_welcome', array( $phone ) ); } else { wp_remote_get( wats_build_url( array( 'templateName' => $template, 'phoneNumber' => wats_normalize_phone( $phone ), 'csvVariables' => $csv ) ), array( 'timeout' => 15 ) ); } }, 10, 1 ); add_action( 'wats_sc_send_welcome', function( $phone ) { $key = '_wats_sc_welcome_data_' . preg_replace( '/[^a-z0-9]/', '', $phone ); $raw = get_option( $key, '' ); if ( ! $raw ) { return; } $data = json_decode( $raw, true ); delete_option( $key ); if ( empty( $data['template'] ) || empty( $data['phone'] ) ) { return; } wp_remote_get( wats_build_url( array( 'templateName' => $data['template'], 'phoneNumber' => wats_normalize_phone( $data['phone'] ), 'csvVariables' => $data['csv'] ?? '' ) ), array( 'timeout' => 15 ) ); }, 10, 1 ); // Hook 8: SureCart Abandoned Cart — checkout created but not paid add_action( 'surecart/checkout_session_created', function( $checkout ) { if ( ! wats_get_event_enabled( 'abandoned_cart' ) ) { return; } $d = wats_sc_to_array( $checkout ); $phone = wats_get_surecart_phone( $checkout ); if ( empty( $phone ) ) { return; } $norm = wats_normalize_phone( $phone ); $args = array( $norm ); $existing = wp_next_scheduled( 'wats_sc_send_abandoned_cart', $args ); if ( $existing ) { wp_unschedule_event( $existing, 'wats_sc_send_abandoned_cart', $args ); } $delay = wats_get_event_delay( 'abandoned_cart' ); $csv = wats_build_csv_for_surecart_event( 'abandoned_cart', $checkout ); update_option( '_wats_sc_abcart_data_' . $norm, json_encode( array( 'phone' => $norm, 'csv' => $csv ) ), false ); wp_schedule_single_event( time() + max( 60, $delay ), 'wats_sc_send_abandoned_cart', $args ); }, 10, 1 ); // Cancel abandoned cart cron when order is completed add_action( 'surecart/checkout_session_completed', function( $checkout ) { $phone = wats_get_surecart_phone( $checkout ); if ( empty( $phone ) ) { return; } $norm = wats_normalize_phone( $phone ); $args = array( $norm ); $existing = wp_next_scheduled( 'wats_sc_send_abandoned_cart', $args ); if ( $existing ) { wp_unschedule_event( $existing, 'wats_sc_send_abandoned_cart', $args ); } delete_option( '_wats_sc_abcart_data_' . $norm ); }, 5, 1 ); add_action( 'wats_sc_send_abandoned_cart', function( $norm_phone ) { $template = wats_get_event_template( 'abandoned_cart' ); if ( $template === '' ) { return; } $raw = get_option( '_wats_sc_abcart_data_' . $norm_phone, '' ); if ( ! $raw ) { return; } // Already cancelled (order was placed) $data = json_decode( $raw, true ); delete_option( '_wats_sc_abcart_data_' . $norm_phone ); wp_remote_get( wats_build_url( array( 'templateName' => $template, 'phoneNumber' => $norm_phone, 'csvVariables' => $data['csv'] ?? '' ) ), array( 'timeout' => 15 ) ); }, 10, 1 ); /** * Fetch templates list from provider. Returns array of template names. * Also caches variable count per template in wats_template_vars_cache. */ function wats_fetch_templates() { $api_key = wats_get_api_key(); if ( $api_key === '' ) { delete_transient( 'wats_templates_cache' ); delete_transient( 'wats_template_vars_cache' ); return array(); } $url = add_query_arg( array( 'api-key' => $api_key ), 'https://verifiedwhatsapp.admarksolution.com/templates' ); $response = wp_remote_get( $url, array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/json' ) ) ); if ( is_wp_error( $response ) ) { return array(); } $code = wp_remote_retrieve_response_code( $response ); if ( $code < 200 || $code >= 300 ) { return array(); } $data = json_decode( wp_remote_retrieve_body( $response ), true ); $out = array(); $var_counts = array(); // template_name => variable count if ( is_array( $data ) ) { foreach ( $data as $item ) { if ( is_string( $item ) ) { $out[] = $item; } elseif ( is_array( $item ) && isset( $item['name'] ) && is_string( $item['name'] ) ) { $name = $item['name']; $out[] = $name; // Detect variable count from components (standard WhatsApp API format) $count = 0; if ( isset( $item['components'] ) && is_array( $item['components'] ) ) { foreach ( $item['components'] as $comp ) { $type = isset( $comp['type'] ) ? strtolower( $comp['type'] ) : ''; if ( $type === 'body' ) { // Count {{n}} placeholders in body text $text = isset( $comp['text'] ) ? $comp['text'] : ''; if ( $text !== '' && preg_match_all( '/\{\{(\d+)\}\}/', $text, $m ) ) { $count = max( $count, max( array_map( 'intval', $m[1] ) ) ); } // Fallback: count from example body_text array if ( isset( $comp['example']['body_text'][0] ) && is_array( $comp['example']['body_text'][0] ) ) { $count = max( $count, count( $comp['example']['body_text'][0] ) ); } } } } // Fallback fields some providers include if ( $count === 0 && isset( $item['variables_count'] ) ) { $count = (int) $item['variables_count']; } if ( $count === 0 && isset( $item['params_count'] ) ) { $count = (int) $item['params_count']; } $var_counts[ $name ] = $count; } } } if ( ! empty( $out ) ) { set_transient( 'wats_templates_cache', $out, 10 * MINUTE_IN_SECONDS ); set_transient( 'wats_template_vars_cache', $var_counts, 10 * MINUTE_IN_SECONDS ); } else { delete_transient( 'wats_templates_cache' ); delete_transient( 'wats_template_vars_cache' ); } return array_values( array_unique( $out ) ); } /** * Get cached variable counts per template name. Returns array: ['name' => count]. */ function wats_get_template_var_counts() { $counts = get_transient( 'wats_template_vars_cache' ); return is_array( $counts ) ? $counts : array(); } /** * AJAX: Fetch variable count for a single template via /template-byname endpoint. */ add_action( 'wp_ajax_wats_get_template_vars', function() { check_ajax_referer( 'wats_ajax_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Unauthorized', 403 ); } $template_name = sanitize_text_field( (string) ( $_GET['template'] ?? '' ) ); if ( $template_name === '' ) { wp_send_json_error( 'Missing template name', 400 ); } $api_key = wats_get_api_key(); if ( $api_key === '' ) { wp_send_json_error( 'No API key configured', 400 ); } $url = add_query_arg( array( 'name' => $template_name, 'api-key' => $api_key ), 'https://verifiedwhatsapp.admarksolution.com/template-byname' ); $response = wp_remote_get( $url, array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/json' ) ) ); if ( is_wp_error( $response ) ) { wp_send_json_error( $response->get_error_message(), 502 ); } $code = wp_remote_retrieve_response_code( $response ); if ( $code < 200 || $code >= 300 ) { wp_send_json_error( 'API error ' . $code, 502 ); } $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $data ) ) { wp_send_json_error( 'Invalid API response', 502 ); } // Parse variable count — try every known response format $var_count = 0; // Format: {"variables": {"count": 3}} if ( isset( $data['variables']['count'] ) ) { $var_count = (int) $data['variables']['count']; } // Format: {"variables": {"1": {...}, "2": {...}}} — object keyed by slot number if ( $var_count === 0 && isset( $data['variables'] ) && is_array( $data['variables'] ) ) { $var_count = count( $data['variables'] ); } // Parse body component for {{n}} placeholders if ( $var_count === 0 && isset( $data['components'] ) && is_array( $data['components'] ) ) { foreach ( $data['components'] as $comp ) { $type = isset( $comp['type'] ) ? strtolower( $comp['type'] ) : ''; if ( $type === 'body' ) { $text = isset( $comp['text'] ) ? $comp['text'] : ''; if ( $text !== '' && preg_match_all( '/\{\{(\d+)\}\}/', $text, $m ) ) { $var_count = max( $var_count, max( array_map( 'intval', $m[1] ) ) ); } if ( isset( $comp['example']['body_text'][0] ) && is_array( $comp['example']['body_text'][0] ) ) { $var_count = max( $var_count, count( $comp['example']['body_text'][0] ) ); } } } } // Fallback scalar fields if ( $var_count === 0 && isset( $data['variables_count'] ) ) { $var_count = (int) $data['variables_count']; } if ( $var_count === 0 && isset( $data['params_count'] ) ) { $var_count = (int) $data['params_count']; } if ( $var_count === 0 && isset( $data['variable_count'] ) ) { $var_count = (int) $data['variable_count']; } // Update the cache $cache = get_transient( 'wats_template_vars_cache' ); if ( ! is_array( $cache ) ) { $cache = array(); } $cache[ $template_name ] = $var_count; set_transient( 'wats_template_vars_cache', $cache, 10 * MINUTE_IN_SECONDS ); wp_send_json_success( array( 'template' => $template_name, 'var_count' => $var_count, 'raw' => $data, ) ); } ); /** * AJAX: अपग्रेड किया गया कैंपेन कस्टमर फ़ेचर (Pagination और Search के साथ) */ add_action( 'wp_ajax_wats_get_campaign_customers', function() { check_ajax_referer( 'wats_ajax_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Unauthorized', 403 ); } // रिक्वेस्ट से पेज नंबर और सर्च टर्म प्राप्त करें $paged = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1; $search = isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : ''; $number = 100; // प्रति पेज ग्राहकों की संख्या $offset = ( $paged - 1 ) * $number; // यूज़र क्वेरी के लिए आर्गुमेंट्स तैयार करें $args = array( 'number' => $number, 'offset' => $offset, 'fields' => array( 'ID', 'user_email', 'display_name' ), ); // अगर सर्च बॉक्स में कुछ टाइप किया गया है if ( ! empty( $search ) ) { $args['search'] = '*' . $search . '*'; $args['search_columns'] = array( 'ID', 'user_login', 'user_email', 'display_name' ); } // वर्डप्रेस डेटाबेस से यूज़र्स फ़ेच करें $user_query = new WP_User_Query( $args ); $customers = $user_query->get_results(); $total_users = $user_query->get_total(); // कुल मैचिंग यूज़र्स की संख्या $customer_list = array(); foreach ( $customers as $user ) { // बिलिंग फ़ोन नंबर निकालें $phone = get_user_meta( $user->ID, 'billing_phone', true ); // अगर फ़ोन नंबर खाली भी है, तब भी लिस्ट में दिखाएँ ताकि एडमिन को पता चले, // लेकिन एक फ्लैग (no_phone) भेज दें ताकि फ्रंटएंड पर उसे सेलेक्ट न किया जा सके। $customer_list[] = array( 'id' => $user->ID, 'name' => $user->display_name ? $user->display_name : 'No Name', 'email' => $user->user_email, 'phone' => ! empty( $phone ) ? $phone : '⚠️ No Phone Registered', 'no_phone' => empty( $phone ) ? true : false, ); } // फ्रंटएंड जावास्क्रिप्ट को पूरा डेटा रिस्पॉन्स भेजें wp_send_json_success( array( 'customers' => $customer_list, 'total_users' => $total_users, 'page' => $paged, 'total_pages' => ceil( $total_users / $number ) ) ); } ); /** * AJAX: Fetch templates for campaign selection. */ add_action( 'wp_ajax_wats_get_campaign_templates', function() { check_ajax_referer( 'wats_ajax_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Unauthorized', 403 ); } $api_key = wats_get_api_key(); if ( $api_key === '' ) { wp_send_json_error( 'No API key configured', 400 ); } $templates = get_transient( 'wats_templates_cache' ); if ( $templates === false ) { $templates = wats_fetch_templates(); } wp_send_json_success( $templates ); } ); /** * Count sends within a date range (inclusive) using UTC-safe comparisons. */ function wats_count_between( $start, $end ) { $log = get_option( 'wats_send_log', array() ); if ( empty( $log ) ) { return 0; } $tz = wp_timezone(); // site timezone $start_dt = $start ? date_create_immutable( $start . ' 00:00:00', $tz ) : null; $end_dt = $end ? date_create_immutable( $end . ' 23:59:59', $tz ) : null; $start_utc = $start_dt ? $start_dt->getTimestamp() - (int) $tz->getOffset( $start_dt ) : PHP_INT_MIN; $end_utc = $end_dt ? $end_dt->getTimestamp() - (int) $tz->getOffset( $end_dt ) : PHP_INT_MAX; $count = 0; foreach ( $log as $entry ) { if ( is_array( $entry ) && isset( $entry['utc'] ) ) { $ts = (int) $entry['utc']; } else { $dt = date_create_immutable( (string) $entry, $tz ); $ts = $dt ? $dt->getTimestamp() - (int) $tz->getOffset( $dt ) : 0; } if ( $ts >= $start_utc && $ts <= $end_utc ) { $count++; } } return $count; } /** * Admin menu: add top-level page and subpages. */ add_action( 'admin_menu', function () { add_menu_page( 'Admark CRM', 'Admark CRM', 'manage_options', 'wats-settings', 'wats_render_settings_page', 'dashicons-whatsapp', 56 ); add_submenu_page( 'wats-settings', 'Settings', 'Settings', 'manage_options', 'wats-settings', 'wats_render_settings_page' ); add_submenu_page( 'wats-settings', 'Automation', 'Automation', 'manage_options', 'wats-automation', 'wats_render_automation_page' ); add_submenu_page( 'wats-settings', 'Message Templates', 'Message Templates', 'manage_options', 'wats-message-templates', 'wats_render_message_templates_page' ); } ); // Enqueue admin styles and scripts properly with structural variables passed directly add_action( 'admin_enqueue_scripts', function( $hook_suffix ) { $pages = array( 'toplevel_page_wats-settings', 'admark-crm_page_wats-automation', 'admark-crm_page_wats-message-templates' ); if ( ! in_array( $hook_suffix, $pages, true ) ) { return; } wp_enqueue_style( 'admark-crm-admin', plugins_url( 'assets/admin.css', __FILE__ ), array(), '3.2.2' ); wp_enqueue_script( 'admark-crm-admin', plugins_url( 'assets/admin.js', __FILE__ ), array('jquery'), '3.2.2', true ); // स्क्रिप्ट को वेरिएबल्स लोकलाइज करें ताकि जावास्क्रिप्ट सीधे इसे पढ़ सके wp_localize_script( 'admark-crm-admin', 'wats_setup_vars', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wats_ajax_nonce' ) )); } ); /** * Register settings. */ add_action( 'admin_init', function () { register_setting( 'wats_settings_group', 'wats_api_key', array( 'sanitize_callback' => 'wats_sanitize_api_key' ) ); register_setting( 'wats_settings_group', 'wats_phone_number_id', array( 'sanitize_callback' => 'wats_sanitize_phone_number_id' ) ); register_setting( 'wats_settings_group', 'wats_selected_template', array( 'sanitize_callback' => 'wats_sanitize_selected_template' ) ); register_setting( 'wats_settings_group', 'wats_send_count', array( 'sanitize_callback' => 'wats_sanitize_send_count' ) ); register_setting( 'wats_settings_group', 'wats_send_log', array( 'sanitize_callback' => 'wats_sanitize_send_log' ) ); foreach ( array_keys( wats_get_events() ) as $event ) { register_setting( 'wats_automation_group', 'wats_enable_' . $event, array( 'sanitize_callback' => 'absint' ) ); register_setting( 'wats_automation_group', 'wats_tpl_' . $event, array( 'sanitize_callback' => 'sanitize_text_field' ) ); register_setting( 'wats_automation_group', 'wats_varcount_' . $event, array( 'sanitize_callback' => 'absint' ) ); register_setting( 'wats_automation_group', 'wats_delay_val_' . $event, array( 'sanitize_callback' => 'absint' ) ); register_setting( 'wats_automation_group', 'wats_delay_unit_' . $event, array( 'sanitize_callback' => 'sanitize_key' ) ); for ( $i = 1; $i <= 10; $i++ ) { register_setting( 'wats_automation_group', 'wats_varmap_' . $event . '_' . $i, array( 'sanitize_callback' => 'sanitize_text_field' ) ); } } // Campaign settings register_setting( 'wats_campaign_group', 'wats_campaign_enabled', array( 'sanitize_callback' => 'absint' ) ); register_setting( 'wats_campaign_group', 'wats_campaign_template', array( 'sanitize_callback' => 'sanitize_text_field' ) ); register_setting( 'wats_campaign_group', 'wats_campaign_customers', array( 'sanitize_callback' => 'wats_sanitize_campaign_customers' ) ); register_setting( 'wats_campaign_group', 'wats_campaign_schedule', array( 'sanitize_callback' => 'wats_sanitize_campaign_schedule' ) ); } ); // ==== SHARED NAV HELPER ==== function wats_render_nav( $active ) { $tabs = array( 'wats-settings' => 'Settings', 'wats-automation' => 'Automation', 'wats-message-templates' => 'Message Templates', ); echo ''; } // ==== SETTINGS PAGE ==== function wats_render_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions', 'admark-crm' ) ); } $api_key_present = ( wats_get_api_key() !== '' ); $templates = $api_key_present ? get_transient( 'wats_templates_cache' ) : array(); if ( $templates === false && $api_key_present ) { $templates = wats_fetch_templates(); } if ( ! $api_key_present ) { delete_transient( 'wats_templates_cache' ); $templates = array(); } if ( ! is_array( $templates ) ) { $templates = array(); } $selected = wats_get_selected_template(); $total_sent = (int) get_option( 'wats_send_count', 0 ); $today_iso = current_time( 'Y-m-d' ); $first_date = ''; $log = get_option( 'wats_send_log', array() ); if ( ! empty( $log ) ) { $min_ts = PHP_INT_MAX; $tz = wp_timezone(); foreach ( $log as $entry ) { if ( is_array( $entry ) && isset( $entry['utc'] ) ) { $min_ts = min( $min_ts, (int) $entry['utc'] ); } else { $dt = date_create_immutable( (string) $entry, $tz ); if ( $dt ) { $min_ts = min( $min_ts, $dt->getTimestamp() - (int) $tz->getOffset( $dt ) ); } } } if ( $min_ts !== PHP_INT_MAX ) { $first_date = wp_date( 'Y-m-d', $min_ts, $tz ); } } $start_date = isset( $_POST['wats_from'] ) ? sanitize_text_field( $_POST['wats_from'] ) : ( $first_date ?: $today_iso ); $end_date = isset( $_POST['wats_to'] ) ? sanitize_text_field( $_POST['wats_to'] ) : $today_iso; if ( $start_date > $today_iso ) { $start_date = $today_iso; } if ( $end_date > $today_iso ) { $end_date = $today_iso; } if ( $end_date < $start_date ) { $end_date = $start_date; } $range_count = wats_count_between( $start_date, $end_date ); ?>

Admark CRM

v2.0.0
Total Messages Sent
Active Default Template
Messages in Range
→

API Configuration

Template Cache

Templates cache cleared. Refreshing…

'; } wp_nonce_field( 'wats_refresh_templates_action' ); ?>

Templates are cached for 10 minutes. Click to force reload from the Admark API.

count] $var_opts = wats_get_variable_options(); // डैसबोर्ड से offer_campaign को मुख्य लूप के लिए अलग करें $automation_events = $events; unset($automation_events['offer_campaign']); $saved_maps_js = array(); $saved_counts_js = array(); foreach ( $events as $ek => $ev ) { $saved_maps_js[ $ek ] = array(); $saved_counts_js[ $ek ] = (int) get_option( 'wats_varcount_' . $ek, 0 ); for ( $i = 1; $i <= 10; $i++ ) { $saved_maps_js[ $ek ][ $i ] = wats_get_var_map( $ek, $i ); } } ?>

Admark CRM

v3.2.2

कृपया पहले Settings में अपना API Key कॉन्फ़िगर करें।

Offer & Festival Campaign Hub

यहाँ से आप अपने सभी ग्राहकों की लिस्ट लोड कर सकते हैं, टेम्पलेट चुनकर वैरिएबल्स मैप कर सकते हैं और तुरंत या शेड्यूल करके संदेश भेज सकते हैं।

Automation Flow

🌐 Visit
→
🛒 Cart
→
⏰ Abandoned
→
✅ Order
→
💳 Payment
→
🚚 Dispatch
→
📦 Delivered
→
⭐ Feedback
$event ) : $enabled = wats_get_event_enabled( $event_key ); $cur_tpl = wats_get_event_template( $event_key ); ?>

$now ) { // कैंपेन डेटा डेटाबेस ऑप्शन में स्टोर करें ताकि क्रॉन उसे चला सके $campaign_id = 'wats_camp_' . time(); update_option( $campaign_id, array( 'template' => $template, 'customers' => $id_array, 'varmaps' => $varmaps ), false ); $timestamp = strtotime( $schedule ); wp_schedule_single_event( $timestamp, 'wats_execute_scheduled_campaign', array( $campaign_id ) ); wp_send_json_success( 'अभियान को सफलतापूर्वक शेड्यूल कर दिया गया है: ' . $schedule ); } // तुरंत भेजने के लिए लूप चलाएं $success_count = 0; foreach ( $id_array as $uid ) { $user = get_userdata( intval( $uid ) ); if ( ! $user ) continue; $phone = get_user_meta( $user->ID, 'billing_phone', true ); if ( empty( $phone ) ) continue; // कस्टम वैरिएबल्स स्ट्रिंग तैयार करें $csv_resolved = array(); foreach ( $varmaps as $slot => $key_field ) { $csv_resolved[] = !empty($key_field) ? wats_resolve_var( $key_field, null, $user ) : ''; } $csv_string = implode( ',', array_map( 'rawurlencode', $csv_resolved ) ); $url = wats_build_url( array( 'templateName' => $template, 'phoneNumber' => wats_normalize_phone( $phone ), 'csvVariables' => $csv_string, ) ); $response = wp_remote_get( $url, array( 'timeout' => 15 ) ); if ( ! is_wp_error( $response ) ) { $code = wp_remote_retrieve_response_code( $response ); if ( $code >= 200 && $code < 300 ) { $success_count++; } } } // स्टेट्स लॉग अपडेट करें if ( $success_count > 0 ) { $count = (int) get_option( 'wats_send_count', 0 ); update_option( 'wats_send_count', $count + $success_count, false ); } wp_send_json_success( $success_count . ' ग्राहकों को सफलतापूर्वक संदेश भेज दिए गए हैं।' ); } ); // शेड्यूल्ड अभियान क्रॉन जॉब प्रोसेसर add_action( 'wats_execute_scheduled_campaign', function( $campaign_id ) { $data = get_option( $campaign_id ); if ( ! $data ) return; foreach ( $data['customers'] as $uid ) { $user = get_userdata( intval( $uid ) ); if ( !$user ) continue; $phone = get_user_meta( $user->ID, 'billing_phone', true ); if ( empty( $phone ) ) continue; $csv_resolved = array(); foreach ( $data['varmaps'] as $slot => $key_field ) { $csv_resolved[] = !empty($key_field) ? wats_resolve_var( $key_field, null, $user ) : ''; } $csv_string = implode( ',', array_map( 'rawurlencode', $csv_resolved ) ); wp_remote_get( wats_build_url( array( 'templateName' => $data['template'], 'phoneNumber' => wats_normalize_phone( $phone ), 'csvVariables' => $csv_string, ) ), array( 'timeout' => 15 ) ); } delete_option( $campaign_id ); } ); // ==== MESSAGE TEMPLATES PAGE ==== function wats_render_message_templates_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions', 'admark-crm' ) ); } $events = wats_get_events(); ?>

Admark CRM

v2.0.0

Sample Message Templates

These are sample messages for reference. Create matching templates in your Admark Dashboard using these formats, then assign them in the Automation tab.

$event ) : ?>