diff --git a/Sources/PediFoods/Services/LocationService.swift b/Sources/PediFoods/Services/LocationService.swift index 11cd212..43d177e 100644 --- a/Sources/PediFoods/Services/LocationService.swift +++ b/Sources/PediFoods/Services/LocationService.swift @@ -75,6 +75,39 @@ final class LocationService: NSObject { #endif } + /// Forward-geocodes a street address into coordinates. Used as a fallback + /// when a saved CustomerAddress has no lat/long (e.g. the CEP lookup at + /// creation time didn't return coordinates) — without this, delivery fee + /// validation silently can't distinguish that address from any other. + static func geocodeAddress( + street: String?, + number: String?, + neighborhood: String?, + city: String?, + state: String?, + zip: String? + ) async -> (Double, Double)? { +#if os(iOS) + let parts = [street, number, neighborhood, city, state, zip] + .compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { $0.isEmpty == false } + guard parts.isEmpty == false else { return nil } + let fullAddress = parts.joined(separator: ", ") + + return await withCheckedContinuation { continuation in + CLGeocoder().geocodeAddressString(fullAddress) { placemarks, error in + guard error == nil, let coordinate = placemarks?.first?.location?.coordinate else { + continuation.resume(returning: nil) + return + } + continuation.resume(returning: (coordinate.latitude, coordinate.longitude)) + } + } +#else + return nil +#endif + } + func requestLocationAsync(timeoutSeconds: TimeInterval = 8) async -> (Double, Double)? { await withCheckedContinuation { continuation in var hasResumed = false diff --git a/Sources/PediFoods/Views/Main/CartView.swift b/Sources/PediFoods/Views/Main/CartView.swift index d104ae3..00e93aa 100644 --- a/Sources/PediFoods/Views/Main/CartView.swift +++ b/Sources/PediFoods/Views/Main/CartView.swift @@ -349,8 +349,29 @@ struct CartView: View { SessionStateStore.saveAddress(appState.address) } - let payloadLat = appState.address.latitude ?? selectedCustomerAddress?.latLong?.first - let payloadLng = appState.address.longitude ?? selectedCustomerAddress?.latLong?.dropFirst().first + var payloadLat = appState.address.latitude ?? selectedCustomerAddress?.latLong?.first + var payloadLng = appState.address.longitude ?? selectedCustomerAddress?.latLong?.dropFirst().first + + // Some saved addresses have no lat/long (CEP lookup at creation + // time didn't return coordinates). Without coordinates the + // backend can't tell this address apart from any other, so the + // fee silently never changes. Geocode locally as a fallback. + if payloadLat == nil || payloadLng == nil { + if let coordinate = await LocationService.geocodeAddress( + street: selectedCustomerAddress?.address, + number: selectedCustomerAddress?.number, + neighborhood: selectedCustomerAddress?.neighborhood, + city: selectedCustomerAddress?.city, + state: selectedCustomerAddress?.state, + zip: selectedCustomerAddress?.zipCode + ) { + payloadLat = coordinate.0 + payloadLng = coordinate.1 + appState.address.latitude = coordinate.0 + appState.address.longitude = coordinate.1 + SessionStateStore.saveAddress(appState.address) + } + } let payload = ValidateDeliveryAddressPayload( address: ValidateDeliveryAddressDataPayload( diff --git a/Sources/PediFoods/Views/Main/CheckoutView+Logic.swift b/Sources/PediFoods/Views/Main/CheckoutView+Logic.swift index 035c256..21151f3 100644 --- a/Sources/PediFoods/Views/Main/CheckoutView+Logic.swift +++ b/Sources/PediFoods/Views/Main/CheckoutView+Logic.swift @@ -164,6 +164,25 @@ extension CheckoutView { baseDeliveryFee = nil + // Some saved addresses have no lat/long (CEP lookup at creation time + // didn't return coordinates). Without coordinates the backend can't + // tell this address apart from any other, so the fee silently never + // changes. Geocode locally as a fallback before validating. + if appState.address.latitude == nil || appState.address.longitude == nil { + if let coordinate = await LocationService.geocodeAddress( + street: selectedCustomerAddress?.address, + number: selectedCustomerAddress?.number, + neighborhood: selectedCustomerAddress?.neighborhood, + city: selectedCustomerAddress?.city, + state: selectedCustomerAddress?.state, + zip: selectedCustomerAddress?.zipCode + ) { + appState.address.latitude = coordinate.0 + appState.address.longitude = coordinate.1 + SessionStateStore.saveAddress(appState.address) + } + } + let payload = ValidateDeliveryAddressPayload( address: ValidateDeliveryAddressDataPayload( street: selectedCustomerAddress?.address,