prepare("SELECT * FROM complaint WHERE id = ? LIMIT 1"); $stmt->execute([$complaintId]); $complaint = $stmt->fetch(PDO::FETCH_ASSOC); if (!$complaint) { jsonError("Complaint not found", 404); } $rideId = $complaint['ride_id'] ?? null; // ══════════════════════════════════════════════════════════ // ١) تحديث القضية // ══════════════════════════════════════════════════════════ $sets = []; $params = []; // ‏أول رد يُختم مرة واحدة ولا يتغيّر — هو مؤشر الـSLA الذي يشعر به // ‏العميل. لمسة ثانية من الموظف لا تعيد ضبطه. if (empty($complaint['first_response_at'])) { $sets[] = "first_response_at = NOW()"; } // ‏من يلمس القضية يملكها. يمنع "الكل مسؤول فلا أحد مسؤول". if (empty($complaint['assigned_to'])) { $sets[] = "assigned_to = ?"; $params[] = $user_id ?? 'support'; } if ($newStatus) { $sets[] = "statusComplaint = ?"; $params[] = $newStatus; if ($newStatus === 'Resolved' && empty($complaint['date_resolved'])) { $sets[] = "date_resolved = NOW()"; } } if ($reasonCode) { $sets[] = "reason_code = ?"; $params[] = $reasonCode; } if ($resolution) { $sets[] = "resolution = ?"; $params[] = mb_substr($resolution, 0, 2000); } if ($faultOn) { $sets[] = "fault_determination = ?"; $params[] = mb_substr($faultOn, 0, 255); } if (!empty($sets)) { $params[] = $complaintId; $con->prepare("UPDATE complaint SET " . implode(', ', $sets) . " WHERE id = ?") ->execute($params); } // ══════════════════════════════════════════════════════════ // ٢) التعويض // ══════════════════════════════════════════════════════════ $compensation = null; if ($compAmount !== null && $compAmount > 0) { if (!in_array($compKind, COMPENSATION_KINDS, true)) { jsonError("Invalid compensation_kind"); } if (!in_array($compBeneficiary, COMPENSATION_BENEFICIARIES, true)) { jsonError("Invalid compensation_beneficiary"); } $beneficiaryId = $compBeneficiary === 'passenger' ? ($complaint['passenger_id'] ?? '') : ($complaint['driver_id'] ?? ''); if (empty($beneficiaryId)) { jsonError("Complaint has no $compBeneficiary to compensate"); } // ‏استرجاع لا يتجاوز قيمة الرحلة: "refund" يعني إعادة ما دُفع، وما // ‏فوقه رصيد اعتذار (goodwill) يُصنَّف كذلك ويُراجَع كذلك. $kazan = $rideId ? getKazanForRide($con, $rideId) : []; $currency = !empty($kazan['currency']) ? (string) $kazan['currency'] : getCurrencyByCountry((string) ($kazan['country'] ?? 'Syria')); // ‏السقف الصلب بعملة الدولة — يُفحص بعد معرفة العملة لا قبلها. // ‏صلاحية صرف المال بيد موظف دعم تحتاج حداً لا يتجاوزه خطأ مطبعي. $hardCap = getMoneyHardCap($currency); if ($compAmount > $hardCap) { jsonError("Compensation exceeds the allowed limit ($hardCap $currency)"); } if ($compKind === 'refund' && $rideId) { $stmtRide = $con->prepare("SELECT price FROM ride WHERE id = ? LIMIT 1"); $stmtRide->execute([$rideId]); $ridePrice = (float) ($stmtRide->fetchColumn() ?: 0); if ($ridePrice > 0 && $compAmount > $ridePrice) { jsonError("Refund cannot exceed the ride price ($ridePrice $currency)." . " Use goodwill for anything above it."); } } // ‏السجل أولاً بحالة pending، ثم التحويل. العكس كان يترك مالاً // ‏مصروفاً بلا أثر إن انقطع التنفيذ بينهما. $insComp = $con->prepare(" INSERT INTO complaint_compensations (complaint_id, ride_id, beneficiary_type, beneficiary_id, kind, amount, currency, issued_by, note) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) "); $insComp->execute([ $complaintId, $rideId, $compBeneficiary, $beneficiaryId, $compKind, $compAmount, $currency, $user_id ?? 'support', $compNote ? mb_substr($compNote, 0, 500) : null, ]); $compId = (int) $con->lastInsertId(); $code = transferCompensation( $con, $rideId, $compBeneficiary, $beneficiaryId, $compAmount, $compId ); $con->prepare(" UPDATE complaint_compensations SET transfer_status = ?, transfer_code = ? WHERE id = ? ")->execute([$code === 200 ? 'success' : 'failed', $code, $compId]); if ($code !== 200) { error_log("[complaint] MONEY: تعويض #$compId بقيمة $compAmount $currency" . " للشكوى #$complaintId لم يصل ($compBeneficiary=$beneficiaryId، رمز=$code)"); } $compensation = [ 'id' => $compId, 'amount' => $compAmount, 'currency' => $currency, 'kind' => $compKind, 'status' => $code === 200 ? 'success' : 'failed', ]; } error_log("[complaint] #$complaintId → " . ($newStatus ?: 'بلا تغيير حالة') . " بواسطة " . ($user_id ?? 'support') . ($compensation ? " (+تعويض {$compensation['amount']})" : "")); jsonSuccess([ 'complaint_id' => $complaintId, 'status' => $newStatus ?: $complaint['statusComplaint'], 'compensation' => $compensation, ], "Complaint updated"); } catch (PDOException $e) { error_log("[complaint/resolve] " . $e->getMessage()); jsonError("DB Error", 500); } /** * ‏يحوّل التعويض إلى محفظة المستفيد. يرجع رمز HTTP من خادم المحفظة. * * ‏paymentID مشتق من رقم التعويض لا من الشكوى: الشكوى الواحدة قد تُعوَّض * ‏مرتين (استرجاع ثم اعتذار)، واشتقاقه من الشكوى كان سيجعل الثانية * ‏تُرفض كمكرّرة. */ function transferCompensation( PDO $con, $rideId, string $beneficiaryType, string $beneficiaryId, float $amount, int $compId ): int { $kazan = $rideId ? getKazanForRide($con, $rideId) : []; $country = strtolower((string) ($kazan['country'] ?? 'jordan')); $walletServer = "https://walletintaleq.intaleq.xyz"; if ($country === 'jordan') { $walletServer = getenv('WALLET_SERVER_JORDAN') ?: $walletServer; } elseif ($country === 'egypt') { $walletServer = getenv('WALLET_SERVER_EGYPT') ?: $walletServer; } else { $walletServer = getenv('WALLET_SERVER_SYRIA') ?: $walletServer; } if ($beneficiaryType === 'driver') { $url = "$walletServer/v2/main/ride/driverWallet/add_s2s_reward.php"; $fields = [ 'driverID' => $beneficiaryId, 'paymentID' => "complaint_comp_$compId", 'amount' => $amount, 'paymentMethod' => 'complaint_compensation', ]; } else { // ‏محفظة الراكب تستقبل الأرصدة كقيمة موجبة على نفس نقطة الدين. $url = "$walletServer/v2/main/ride/passengerWallet/add_s2s_debt.php"; $fields = ['passengerID' => $beneficiaryId, 'amount' => $amount]; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($fields), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 8, CURLOPT_HTTPHEADER => [ 'Content-Type: application/x-www-form-urlencoded', 'X-S2S-Api-Key: ' . getenv('S2S_SHARED_KEY'), ], ]); curl_exec($ch); $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $code; }