fix: align OTP auth with actual API response format
Three bugs found and fixed:
1. sendOTP was sending "ContactNumber" but API expects "Phone"
2. APIResponse expected {"Success":true,"Data":{}} but API returns {"OK":true,"UUID":"..."}
3. verifyOTP was sending "Code" but API expects "OTP"
Now decodes the raw API format directly instead of going through the
generic APIResponse wrapper (which doesn't match auth endpoints).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fd4b1bf8ca
commit
2242260f5a
1 changed files with 56 additions and 19 deletions
|
|
@ -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<OTPResponse>.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<LoginResponse>.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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue