| @@ -131,9 +131,17 @@ | ||
| 131 | 131 | $order_number = isset($params['order_number']) ? sanitize_text_field($params['order_number']) : ''; |
| 132 | 132 | $email = isset($params['email']) ? sanitize_email($params['email']) : ''; |
| 133 | 133 | // last_name is accepted but is NOT a standalone key; the email is always required. |
| 134 | 134 | $last_name = isset($params['last_name']) ? sanitize_text_field($params['last_name']) : ''; |
| 135 | + $action = isset($params['action']) ? sanitize_text_field($params['action']) : ''; | |
| 135 | 136 | |
| 137 | + // "My recent orders" for a signed-in customer: email-only, no order number. Safe because the | |
| 138 | + // onWebChat server only ever sends a VERIFIED email here (it recomputed the identity hash this | |
| 139 | + // plugin emitted for the logged-in customer), and the request itself is HMAC-authenticated. | |
| 140 | + if ($action === 'recent_orders') { | |
| 141 | + return $this->handle_recent_orders($email); | |
| 142 | + } | |
| 143 | + | |
| 136 | 144 | // Both the order number AND the email are required for verification. |
| 137 | 145 | if (empty($order_number) || empty($email)) { |
| 138 | 146 | return new WP_REST_Response(array('found' => false, 'verified' => false, 'error' => 'need_more_info'), 200); |
| 139 | 147 | } |
| @@ -179,9 +187,56 @@ | ||
| 179 | 187 | |
| 180 | 188 | // Successful match: clear the per-order failed-attempt counter. |
| 181 | 189 | delete_transient($fail_key); |
| 182 | 190 | |
| 183 | - // Build the (verified) response. | |
| 191 | + return new WP_REST_Response($this->build_order_response($order), 200); | |
| 192 | + } | |
| 193 | + | |
| 194 | + /** | |
| 195 | + * List the customer's most recent orders by email (action=recent_orders). Called (already | |
| 196 | + * HMAC-authenticated) only with server-verified emails, so no per-order fail counting applies; | |
| 197 | + * the response is always scoped to exactly that email. | |
| 198 | + * | |
| 199 | + * @param string $email | |
| 200 | + * @return WP_REST_Response | |
| 201 | + */ | |
| 202 | + private function handle_recent_orders($email) { | |
| 203 | + if (empty($email)) { | |
| 204 | + return new WP_REST_Response(array('found' => false, 'verified' => false, 'error' => 'need_more_info'), 200); | |
| 205 | + } | |
| 206 | + | |
| 207 | + $wc_orders = wc_get_orders(array( | |
| 208 | + 'billing_email' => $email, | |
| 209 | + 'limit' => 5, | |
| 210 | + 'orderby' => 'date', | |
| 211 | + 'order' => 'DESC', | |
| 212 | + 'type' => 'shop_order', // exclude refund objects | |
| 213 | + )); | |
| 214 | + | |
| 215 | + $orders = array(); | |
| 216 | + foreach ($wc_orders as $order) { | |
| 217 | + // Never expose unfinished checkout drafts. | |
| 218 | + if ($order->get_status() === 'checkout-draft') { | |
| 219 | + continue; | |
| 220 | + } | |
| 221 | + $orders[] = $this->build_order_response($order); | |
| 222 | + } | |
| 223 | + | |
| 224 | + if (empty($orders)) { | |
| 225 | + return new WP_REST_Response(array('found' => false, 'verified' => true, 'orders' => array()), 200); | |
| 226 | + } | |
| 227 | + | |
| 228 | + return new WP_REST_Response(array('found' => true, 'verified' => true, 'orders' => $orders), 200); | |
| 229 | + } | |
| 230 | + | |
| 231 | + /** | |
| 232 | + * Build the verified per-order payload in the shape the onWebChat server normalizer expects. | |
| 233 | + * Used by both the single-order lookup and the recent-orders listing. | |
| 234 | + * | |
| 235 | + * @param WC_Order $order | |
| 236 | + * @return array | |
| 237 | + */ | |
| 238 | + private function build_order_response($order) { | |
| 184 | 239 | $items = array(); |
| 185 | 240 | foreach ($order->get_items() as $item) { |
| 186 | 241 | $items[] = array( |
| 187 | 242 | 'name' => $item->get_name(), |
| @@ -192,24 +247,57 @@ | ||
| 192 | 247 | } |
| 193 | 248 | } |
| 194 | 249 | |
| 195 | 250 | $status = $order->get_status(); // e.g. 'processing', 'completed' |
| 196 | - $data = array( | |
| 197 | - 'found' => true, | |
| 198 | - 'verified' => true, | |
| 199 | - 'order_number' => (string) $order->get_order_number(), | |
| 200 | - 'status' => $status, | |
| 201 | - 'status_label' => function_exists('wc_get_order_status_name') ? wc_get_order_status_name($status) : $status, | |
| 202 | - 'date_created' => $order->get_date_created() ? wc_format_datetime($order->get_date_created()) : '', | |
| 203 | - 'total' => $order->get_total(), | |
| 204 | - 'currency' => $order->get_currency(), | |
| 205 | - 'payment_method' => $order->get_payment_method_title(), | |
| 206 | - 'customer_note' => $order->get_customer_note(), | |
| 207 | - 'items' => $items, | |
| 208 | - 'tracking' => $this->get_tracking($order), | |
| 251 | + return array( | |
| 252 | + 'found' => true, | |
| 253 | + 'verified' => true, | |
| 254 | + 'order_number' => (string) $order->get_order_number(), | |
| 255 | + 'status' => $status, | |
| 256 | + 'status_label' => function_exists('wc_get_order_status_name') ? wc_get_order_status_name($status) : $status, | |
| 257 | + 'date_created' => $order->get_date_created() ? wc_format_datetime($order->get_date_created()) : '', | |
| 258 | + 'total' => $order->get_total(), | |
| 259 | + 'currency' => $order->get_currency(), | |
| 260 | + 'payment_method' => $order->get_payment_method_title(), | |
| 261 | + 'shipping_method' => $order->get_shipping_method(), | |
| 262 | + 'customer_note' => $order->get_customer_note(), | |
| 263 | + 'items' => $items, | |
| 264 | + 'tracking' => $this->get_tracking($order), | |
| 265 | + 'history' => $this->get_history($order), | |
| 209 | 266 | ); |
| 267 | + } | |
| 210 | 268 | |
| 211 | - return new WP_REST_Response($data, 200); | |
| 269 | + /** | |
| 270 | + * Customer-facing order notes as a status timeline: lets the AI explain how the order has | |
| 271 | + * progressed, and merchants often paste carrier + tracking info into these notes. ONLY notes of | |
| 272 | + * type "customer" are exposed (internal/private notes may hold merchant-only info). Oldest first, | |
| 273 | + * capped at the 6 most recent. | |
| 274 | + * | |
| 275 | + * @param WC_Order $order | |
| 276 | + * @return array | |
| 277 | + */ | |
| 278 | + private function get_history($order) { | |
| 279 | + $history = array(); | |
| 280 | + | |
| 281 | + if (!function_exists('wc_get_order_notes')) { | |
| 282 | + return $history; | |
| 283 | + } | |
| 284 | + | |
| 285 | + $notes = wc_get_order_notes(array( | |
| 286 | + 'order_id' => $order->get_id(), | |
| 287 | + 'type' => 'customer', | |
| 288 | + 'limit' => 6, | |
| 289 | + )); | |
| 290 | + | |
| 291 | + foreach (array_reverse($notes) as $note) { | |
| 292 | + $history[] = array( | |
| 293 | + 'date' => isset($note->date_created) && $note->date_created ? wc_format_datetime($note->date_created) : '', | |
| 294 | + 'status' => '', | |
| 295 | + 'comment' => isset($note->content) ? (string) $note->content : '', | |
| 296 | + ); | |
| 297 | + } | |
| 298 | + | |
| 299 | + return $history; | |
| 212 | 300 | } |
| 213 | 301 | |
| 214 | 302 | /** |
| 215 | 303 | * Resolve an order number (which may be a custom/sequential number, possibly with a leading #) |