From 6b777752381d2bfaa231b14c75cd6bd24faaf615 Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Fri, 10 Jul 2026 10:55:28 -0300 Subject: [PATCH] [cart-checkout] Geocode addresses locally when lat/long is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explains why the delivery fee specifically never changed while other address info (label, street) updated fine after the previous fix: AddAddressFormView only sets latLong when the CEP lookup happens to return coordinates (AddAddressFormView.swift:141-146) — plenty of saved addresses have none. Without coordinates the backend can't distinguish that address from the previous one, so the fee (and, in Checkout, checkoutAddressWatchKey itself) never actually changes, without any error surfacing since it likely falls back to some default fee instead of rejecting. Added LocationService.geocodeAddress(street:number:neighborhood:city: state:zip:), a thin CLGeocoder wrapper, and call it in both CartView.refreshDeliveryFee and CheckoutView+Logic. validateDeliveryAddressIfNeeded whenever coordinates are missing, persisting the result back into appState.address so it doesn't need to re-geocode on every subsequent check. --- .../PediFoods/Services/LocationService.swift | 33 +++++++++++++++++++ Sources/PediFoods/Views/Main/CartView.swift | 25 ++++++++++++-- .../Views/Main/CheckoutView+Logic.swift | 19 +++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) 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,