public static function create($data){ global $wpdb; if(!is_array($data)) $data=[]; $service_id=absint($data['service_id'] ?? $data['service'] ?? $data['serviceId'] ?? 0); $date=sanitize_text_field($data['date'] ?? $data['appointment_date'] ?? $data['booking_date'] ?? ''); $time=sanitize_text_field($data['time'] ?? $data['start_time'] ?? $data['booking_time'] ?? ''); $name=sanitize_text_field($data['name'] ?? $data['full_name'] ?? $data['customer_name'] ?? ''); $email=sanitize_email($data['email'] ?? $data['customer_email'] ?? ''); $phone=sanitize_text_field($data['phone'] ?? $data['customer_phone'] ?? ''); $notes=sanitize_textarea_field($data['notes'] ?? $data['message'] ?? $data['customer_notes'] ?? ''); // Frontend se jo Square Token aaya hai usay capture karein $square_token = sanitize_text_field($data['square_payment_token'] ?? ''); // Normalize HH:mm:ss to HH:mm if(preg_match('/^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/',$time)) $time=substr($time,0,5); $custom_fields=[]; $raw_custom=$data['custom_fields'] ?? $data['customFields'] ?? []; if(is_string($raw_custom)){ $decoded=json_decode(wp_unslash($raw_custom),true); $raw_custom=is_array($decoded)?$decoded:[]; } if(is_array($raw_custom)) foreach($raw_custom as $k=>$v) { $key=sanitize_key($k); if($key==='') continue; $custom_fields[$key]=is_array($v)?array_map('sanitize_text_field',$v):sanitize_text_field($v); } if(!$service_id) return new \WP_Error('required_service','Please select a service.',['status'=>400]); if(!preg_match('/^\d{4}-\d{2}-\d{2}$/',$date)) return new \WP_Error('invalid_date','Please select a valid date.',['status'=>400]); if(!preg_match('/^\d{2}:\d{2}$/',$time)) return new \WP_Error('invalid_time','Please select a valid start time.',['status'=>400]); if(!$name) return new \WP_Error('required_name','Please enter your full name.',['status'=>400]); if(!is_email($email)) return new \WP_Error('invalid_email','Please enter a valid email address.',['status'=>400]); if(!$phone) return new \WP_Error('required_phone','Please enter your phone number.',['status'=>400]); $date_obj=\DateTime::createFromFormat('!Y-m-d',$date,wp_timezone()); if(!$date_obj || $date_obj->format('Y-m-d')!==$date) return new \WP_Error('invalid_date','Please select a valid date.',['status'=>400]); $service=Services::get($service_id); if(!$service||!$service->active) return new \WP_Error('invalid_service','Selected service is unavailable.',['status'=>400]); $mobility=!empty($data['mobility_service']) && !in_array($data['mobility_service'],['0',0,false,'false','off','no',''],true) ? 1 : 0; $service_price=self::price_to_number($service->price); $mobility_price=$mobility ? 30.00 : 0.00; $total_price=$service_price+$mobility_price; $custom_fields['mobility_service']=$mobility ? 'Add Mobility Service (+$30)' : 'No'; // ========================================================================= // SQUARE PAYMENT PROCESSING (Yahan Square API call hogi) // ========================================================================= $payment_status = 'unpaid'; $square_transaction_id = ''; if ($total_price > 0) { if (empty($square_token)) { return new \WP_Error('required_payment', 'Payment token is missing. Please provide valid card details.', ['status' => 400]); } $square_env = Database::get_setting('square_environment', 'sandbox'); $access_token = Database::get_setting('square_access_token'); $location_id = Database::get_setting('square_location_id'); if (empty($access_token) || empty($location_id)) { return new \WP_Error('square_config_error', 'Square payment gateway is not configured properly.', ['status' => 500]); } // Square API Endpoint (Sandbox vs Production) $api_url = ($square_env === 'production') ? 'https://connect.squareup.com/v2/payments' : 'https://connect.squareupsandbox.com/v2/payments'; // Amount cents mein convert hoti hai (e.g., $50.00 = 5000 cents) $amount_cents = round($total_price * 100); $empotency_key = wp_generate_uuid4(); $body = [ 'source_id' => $square_token, 'idempotency_key' => $empotency_key, 'amount_money' => [ 'amount' => $amount_cents, 'currency' => 'USD' // Apni currency ke mutabiq set karein ], 'location_id' => $location_id, 'autocomplete' => true ]; $response = wp_remote_post($api_url, [ 'headers' => [ 'Authorization' => 'Bearer ' . $access_token, 'Content-Type' => 'application/json', 'Square-Version' => '2024-01-18' ], 'body' => wp_json_encode($body), 'timeout' => 45 ]); if (is_wp_error($response)) { return new \WP_Error('payment_connection_error', 'Could not connect to payment gateway: ' . $response->get_error_message(), ['status' => 500]); } $response_code = wp_remote_retrieve_response_code($response); $response_body = json_decode(wp_remote_retrieve_body($response), true); if ($response_code !== 200 || isset($response_body['errors'])) { $err_msg = $response_body['errors'][0]['detail'] ?? 'Payment failed to process.'; return new \WP_Error('payment_declined', 'Square Error: ' . $err_msg, ['status' => 400]); } // Agar payment successful ho jaye if (isset($response_body['payment']) && $response_body['payment']['status'] === 'COMPLETED') { $payment_status = 'paid'; $square_transaction_id = sanitize_text_field($response_body['payment']['id']); } else { return new \WP_Error('payment_incomplete', 'Payment could not be completed.', ['status' => 400]); } } // ========================================================================= $lock_key='hbr_booking_'.md5($date); $locked=(int)$wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s,10)',$lock_key)); if(!$locked) return new \WP_Error('busy','Please try again in a moment.',['status'=>409]); try { $wpdb->query('START TRANSACTION'); $slot=Availability::assert_available($service_id,$date,$time); if(!$slot){$wpdb->query('ROLLBACK'); return new \WP_Error('slot_unavailable','That time is no longer available. Please choose another time.',['status'=>409]);} $tz=wp_timezone(); $start=new \DateTime($date.' '.$time.':00',$tz); $end=(clone $start)->modify('+'.$duration.' minutes'); $occStart=(clone $start)->modify('-'.(int)$service->buffer_before.' minutes'); $occEnd=(clone $end)->modify('+'.(int)$service->buffer_after.' minutes'); $table=Database::table('bookings'); $conf=$wpdb->get_var($wpdb->prepare("SELECT id FROM $table WHERE status NOT IN ('cancelled') AND occupied_start < %s AND occupied_end > %s LIMIT 1",$occEnd->format('Y-m-d H:i:s'),$occStart->format('Y-m-d H:i:s'))); if($conf){$wpdb->query('ROLLBACK'); return new \WP_Error('slot_unavailable','That time is no longer available. Please choose another time.',['status'=>409]);} $code='HBR-'.str_pad((string)wp_rand(1,999999),6,'0',STR_PAD_LEFT); for($i=0;$i<5;$i++){ if(!(int)$wpdb->get_var($wpdb->prepare("SELECT id FROM $table WHERE booking_code=%s",$code))) break; $code='HBR-'.str_pad((string)wp_rand(1,999999),6,'0',STR_PAD_LEFT); } $status=Database::get_setting('default_booking_status','confirmed'); if(!in_array($status,['pending','confirmed'],true)) $status='confirmed'; // Database insert mein payment_status aur square_transaction_id add kar diye gaye hain $ok=$wpdb->insert($table,[ 'booking_code'=>$code,'service_id'=>$service_id,'customer_name'=>$name,'customer_email'=>$email,'customer_phone'=>$phone, 'customer_notes'=>$notes,'custom_field_data'=>wp_json_encode($custom_fields),'service_price'=>$service_price, 'mobility_service'=>$mobility,'mobility_price'=>$mobility_price,'total_price'=>$total_price,'appointment_date'=>$date, 'start_datetime'=>$start->format('Y-m-d H:i:s'),'end_datetime'=>$end->format('Y-m-d H:i:s'),'occupied_start'=>$occStart->format('Y-m-d H:i:s'), 'occupied_end'=>$occEnd->format('Y-m-d H:i:s'),'status'=>$status,'calendar_sync_status'=>GoogleCalendar::connected()?'pending':'not_connected', 'payment_status'=>$payment_status, 'square_transaction_id'=>$square_transaction_id, 'created_at'=>current_time('mysql'),'updated_at'=>current_time('mysql') ]); if(!$ok){ $db_error=$wpdb->last_error; $wpdb->query('ROLLBACK'); Logger::write('error','booking_db_insert_failed','Unable to save booking',['db_error'=>$db_error,'data'=>['service_id'=>$service_id,'date'=>$date,'time'=>$time]]); return new \WP_Error('db_error','Unable to save the booking right now. Please try again.',['status'=>500]); } $id=(int)$wpdb->insert_id; $wpdb->query('COMMIT'); Logger::write('info','booking_created','Booking created',['booking_id'=>$id,'booking_code'=>$code]); do_action('hbr_booking_created',$id); return self::get($id); } catch(\Throwable $e) { $wpdb->query('ROLLBACK'); Logger::write('error','booking_exception',$e->getMessage(),['file'=>$e->getFile(),'line'=>$e->getLine()]); return new \WP_Error('booking_exception','Unable to create the booking. Please try again.',['status'=>500]); } finally { $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)',$lock_key)); } }