diff --git a/PayfritBeacon/Services/APIClient.swift b/PayfritBeacon/Services/APIClient.swift index 1c04081..6c3522c 100644 --- a/PayfritBeacon/Services/APIClient.swift +++ b/PayfritBeacon/Services/APIClient.swift @@ -36,39 +36,76 @@ actor APIClient { // MARK: - Auth - struct OTPResponse: Codable { - let uuid: String? + /// Raw response from /auth/loginOTP.php + /// API returns: { "OK": true, "UUID": "...", "MESSAGE": "..." } + private struct OTPRawResponse: Codable { + let OK: Bool let UUID: String? - var otpUUID: String { uuid ?? UUID ?? "" } + let MESSAGE: String? + let ERROR: String? } func sendOTP(phone: String) async throws -> String { - let body: [String: Any] = ["ContactNumber": phone] + let body: [String: Any] = ["Phone": phone] let data = try await post(path: "/auth/loginOTP.php", body: body) - let resp = try JSONDecoder().decode(APIResponse.self, from: data) - guard resp.success, let payload = resp.data else { - throw APIError.serverError(resp.message ?? "Failed to send OTP") + let resp = try JSONDecoder().decode(OTPRawResponse.self, from: data) + guard resp.OK, let uuid = resp.UUID, !uuid.isEmpty else { + throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to send OTP") } - return payload.otpUUID + return uuid } - struct LoginResponse: Codable { - let token: String? + /// Raw response from /auth/verifyLoginOTP.php + /// API returns: { "OK": true, "UserID": 123, "Token": "...", "FirstName": "..." } + private struct VerifyOTPRawResponse: Codable { + let OK: Bool let Token: String? - let userId: String? - let UserID: String? - var authToken: String { token ?? Token ?? "" } - var authUserId: String { userId ?? UserID ?? "" } + let UserID: IntOrString? + let MESSAGE: String? + let ERROR: String? + } + + /// Handles UserID coming as either int or string from API + enum IntOrString: Codable { + case int(Int) + case string(String) + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let i = try? container.decode(Int.self) { + self = .int(i) + } else if let s = try? container.decode(String.self) { + self = .string(s) + } else { + self = .string("") + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .int(let i): try container.encode(i) + case .string(let s): try container.encode(s) + } + } + + var stringValue: String { + switch self { + case .int(let i): return String(i) + case .string(let s): return s + } + } } func verifyOTP(uuid: String, code: String) async throws -> (token: String, userId: String) { - let body: [String: Any] = ["UUID": uuid, "Code": code] + let body: [String: Any] = ["UUID": uuid, "OTP": code] let data = try await post(path: "/auth/verifyLoginOTP.php", body: body) - let resp = try JSONDecoder().decode(APIResponse.self, from: data) - guard resp.success, let payload = resp.data else { - throw APIError.serverError(resp.message ?? "Invalid OTP") + let resp = try JSONDecoder().decode(VerifyOTPRawResponse.self, from: data) + guard resp.OK, let token = resp.Token, !token.isEmpty else { + throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Invalid OTP") } - return (payload.authToken, payload.authUserId) + let userId = resp.UserID?.stringValue ?? "" + return (token, userId) } // MARK: - Businesses