$raw = (string) wp_remote_retrieve_body($response); if ($code < 200 || $code >= 300) { return new WP_Error('http_error', 'Google APIがHTTP ' . $code . 'を返しました。'); } $data = json_decode($raw, true); // 空き枠取得は予約登録と異なり書き込みを行わないため、 // Googleが一時的にHTMLを返した場合に限り1回だけ安全に再試行する。 if (!is_array($data) && ($payload['action'] ?? '') === 'getAvailableSlots') { usleep(250000); $retry = $this->post_then_get_redirects($opt['url'], $body); if (empty($retry['error'])) { $retry_response = $retry['response']; $retry_code = (int) wp_remote_retrieve_response_code($retry_response); if ($retry_code >= 200 && $retry_code < 300) { $data = json_decode((string) wp_remote_retrieve_body($retry_response), true); } } } if (!is_array($data)) { return new WP_Error('invalid_json', 'Google APIからJSON以外の応答が返りました。応答先を再デプロイしてください。'); } return $data; } public function ajax_health() { $this->verify_request(); $result = $this->api_request(['action' => 'healthCheck']); if (is_wp_error($result)) { wp_send_json_error([ 'message' => $result->get_error_message(), 'code' => $result->get_error_code(), ], 500); } // Only a confirmed reservation may mark an anonymous visit as completed. do_action('helkema_reservation_created', sanitize_text_field(wp_unslash($_POST['analyticsVisitorId'] ?? ''))); wp_send_json_success($result); } public function ajax_slots() { $this->verify_request(); $category = sanitize_text_field(wp_unslash($_POST['category'] ?? '')); $name = sanitize_text_field(wp_unslash($_POST['name'] ?? '')); $email = sanitize_email(wp_unslash($_POST['email'] ?? '')); $phone = sanitize_text_field(wp_unslash($_POST['phone'] ?? '')); $result = $this->api_request([ 'action' => 'getAvailableSlots', 'category' => $category, 'name' => $name, 'email' => $email, 'phone' => $phone, ]); if (is_wp_error($result)) { wp_send_json_error(['message' => $result->get_error_message()], 500); } if (empty($result['ok'])) { wp_send_json_error(['message' => $result['message'] ?? '空き枠を取得できませんでした。'], 400); } wp_send_json_success([ 'slots' => $result['slots'] ?? [], 'continuity' => $result['continuity'] ?? [ 'returning' => false, 'preferredStaff' => '', 'hasPreferredSlots' => false, ], ]); } public function ajax_submit() { $this->verify_request(); if (!empty($_POST['website'])) { wp_send_json_error(['message' => '送信できませんでした。'], 400); } $data = [ 'action' => 'createReservation', 'category' => sanitize_text_field(wp_unslash($_POST['category'] ?? '')), 'slot' => sanitize_text_field(wp_unslash($_POST['slot'] ?? '')), 'name' => sanitize_text_field(wp_unslash($_POST['name'] ?? '')), 'email' => sanitize_email(wp_unslash($_POST['email'] ?? '')), 'phone' => sanitize_text_field(wp_unslash($_POST['phone'] ?? '')), 'city' => sanitize_text_field(wp_unslash($_POST['city'] ?? '')), 'gender' => sanitize_text_field(wp_unslash($_POST['gender'] ?? '')), 'ageGroup' => sanitize_text_field(wp_unslash($_POST['ageGroup'] ?? '')), 'summary' => sanitize_textarea_field(wp_unslash($_POST['summary'] ?? '')), 'consent' => !empty($_POST['consent']), 'allowDifferentStaff' => !empty($_POST['allowDifferentStaff']), ]; if (!$data['category'] || !$data['slot'] || !$data['name'] || !is_email($data['email']) || !$data['phone'] || !$data['consent']) { wp_send_json_error(['message' => '必須項目をご確認ください。'], 422); } $result = $this->api_request($data); if (is_wp_error($result)) { wp_send_json_error(['message' => $result->get_error_message()], 500); } if (empty($result['ok'])) { wp_send_json_error(['message' => $result['message'] ?? '予約を受け付けられませんでした。'], 400); } wp_send_json_success($result); } } register_activation_hook(__FILE__, ['HELKEMA_Reservation_V508', 'activate']); new HELKEMA_Reservation_V508();