get_billing_email() ) ); codg_list_add( 'blacklist_ips', $order->get_customer_ip_address() ); $order->add_order_note( __( 'Customer blacklisted by OrderProof.', 'orderproof' ) ); } /** * Store's base country (e.g. "MA", "US"). */ function codg_store_country() { return WC()->countries ? WC()->countries->get_base_country() : ''; } /** * International calling code for a country, digits only (e.g. "MA" -> "212", "US" -> "1"). */ function codg_calling_code( $country ) { if ( '' === (string) $country || ! WC()->countries ) { return ''; } $code = WC()->countries->get_country_calling_code( $country ); if ( is_array( $code ) ) { $code = reset( $code ); } return preg_replace( '/\D+/', '', (string) $code ); } /** * Normalize a phone number to international digits (E.164 without "+"). * The customer's billing country decides the calling code: "0612345678" in MA -> "212612345678", * "(415) 555-0123" in US -> "14155550123". Numbers typed with "+" or "00" are kept as international. * * @param string $phone Raw phone number. * @param string $country Billing country; defaults to the store's country. */ function codg_normalize_phone( $phone, $country = '' ) { $raw = trim( (string) $phone ); $digits = preg_replace( '/\D+/', '', $raw ); if ( '' === $digits ) { return ''; } if ( 0 === strpos( $raw, '+' ) ) { return $digits; } if ( 0 === strpos( $digits, '00' ) ) { return substr( $digits, 2 ); } $cc = codg_calling_code( $country ? $country : codg_store_country() ); if ( '' === $cc ) { return $digits; } // Already international without "+": starts with the calling code and is long enough. if ( 0 === strpos( $digits, $cc ) && strlen( $digits ) >= strlen( $cc ) + 8 ) { return $digits; } return $cc . ltrim( $digits, '0' ); } /** * National part of a normalized phone number (calling code removed). */ function codg_national_number( $intl, $country = '' ) { $cc = codg_calling_code( $country ? $country : codg_store_country() ); if ( '' !== $cc && 0 === strpos( $intl, $cc ) ) { return substr( $intl, strlen( $cc ) ); } return $intl; } /** * Normalized phone of an order (stored at checkout, computed for older orders). */ function codg_order_phone( WC_Order $order ) { $phone = $order->get_meta( '_codg_phone' ); return $phone ? $phone : codg_normalize_phone( $order->get_billing_phone(), $order->get_billing_country() ); } /** * Mask a phone for display to customers: 212612345678 -> +212••••••678. */ function codg_mask_phone( $intl ) { $len = strlen( $intl ); if ( $len < 7 ) { return '+' . $intl; } return '+' . substr( $intl, 0, 3 ) . str_repeat( '•', $len - 6 ) . substr( $intl, -3 ); } /** * wc_get_orders() arguments that match orders by normalized phone. * HPOS understands meta_query; the legacy (posts) store ignores it, so there a custom * query var is translated into a meta query by codg_cpt_phone_query(). */ function codg_phone_query_args( $phone ) { if ( class_exists( \Automattic\WooCommerce\Utilities\OrderUtil::class ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) { return array( 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery array( 'key' => '_codg_phone', 'value' => $phone, ), ), ); } return array( 'codg_phone' => $phone ); } add_filter( 'woocommerce_order_data_store_cpt_get_orders_query', 'codg_cpt_phone_query', 10, 2 ); function codg_cpt_phone_query( $query, $query_vars ) { if ( ! empty( $query_vars['codg_phone'] ) ) { $query['meta_query'][] = array( 'key' => '_codg_phone', 'value' => $query_vars['codg_phone'], ); } return $query; } /** * Statuses that count as a failed / refused delivery. */ function codg_failed_statuses() { return apply_filters( 'codg_failed_statuses', array( 'cancelled', 'failed', 'codg-refused' ) ); } /** * Order history for a phone number. * * @return array{total:int,delivered:int,failed:int,pending:int} */ function codg_customer_history( $phone, $exclude_id = 0 ) { $stats = array( 'total' => 0, 'delivered' => 0, 'failed' => 0, 'pending' => 0, ); if ( '' === (string) $phone ) { return $stats; } // The orders list asks once per row; repeat customers share one lookup per request. // Cleared whenever an order is created or changes status (codg_flush_history_cache). global $codg_history_cache; $cache_key = $phone . '|' . (int) $exclude_id; if ( isset( $codg_history_cache[ $cache_key ] ) ) { return $codg_history_cache[ $cache_key ]; } $orders = wc_get_orders( array( 'limit' => 200, 'status' => array_keys( wc_get_order_statuses() ), ) + codg_phone_query_args( $phone ) ); foreach ( $orders as $order ) { if ( (int) $order->get_id() === (int) $exclude_id ) { continue; } // Orders never verified on WhatsApp say nothing about the customer. if ( $order->has_status( 'codg-otp' ) ) { continue; } $stats['total']++; $status = $order->get_status(); if ( 'completed' === $status ) { $stats['delivered']++; } elseif ( in_array( $status, codg_failed_statuses(), true ) ) { $stats['failed']++; } else { $stats['pending']++; } } $codg_history_cache[ $cache_key ] = $stats; return $stats; } function codg_flush_history_cache() { $GLOBALS['codg_history_cache'] = array(); } add_action( 'woocommerce_order_status_changed', 'codg_flush_history_cache' ); add_action( 'woocommerce_new_order', 'codg_flush_history_cache' ); add_action( 'woocommerce_update_order', 'codg_flush_history_cache' ); /** * Default WhatsApp confirmation message. */ function codg_default_template() { return __( "Hello {name} 👋\nThank you for your order #{order_id} at {shop}.\n\nProducts: {items}\nTotal: {total}\nDelivery address: {address}\n\nPlease reply YES to confirm your order.", 'orderproof' ); } /** * Replace {placeholders} in a message template with order data. */ function codg_render_template( $template, WC_Order $order ) { $items = array(); foreach ( $order->get_items() as $item ) { $items[] = $item->get_name() . ' x' . $item->get_quantity(); } $total = html_entity_decode( wp_strip_all_tags( wc_price( $order->get_total(), array( 'currency' => $order->get_currency() ) ) ), ENT_QUOTES, 'UTF-8' ); $address = implode( ', ', array_filter( array( $order->get_billing_address_1(), $order->get_billing_city() ) ) ); $replacements = apply_filters( 'codg_template_placeholders', array( '{name}' => $order->get_billing_first_name(), '{order_id}' => $order->get_order_number(), '{total}' => $total, '{items}' => implode( ', ', $items ), '{address}' => $address, '{phone}' => $order->get_billing_phone(), '{shop}' => get_bloginfo( 'name' ), ), $order ); return strtr( $template, $replacements ); } /** * wa.me click-to-chat link for an order. */ function codg_whatsapp_url( WC_Order $order, $template = null ) { if ( null === $template ) { $template = codg_opt( 'wa_template', codg_default_template() ); } $phone = codg_order_phone( $order ); $message = codg_render_template( $template, $order ); return 'https://wa.me/' . rawurlencode( $phone ) . '?text=' . rawurlencode( $message ); }