[cart-checkout] Geocode addresses locally when lat/long is missing

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.
This commit is contained in:
Daniel Arantes Loverde
2026-07-10 10:55:28 -03:00
parent f706492823
commit 6b77775238
3 changed files with 75 additions and 2 deletions

View File

@@ -75,6 +75,39 @@ final class LocationService: NSObject {
#endif #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)? { func requestLocationAsync(timeoutSeconds: TimeInterval = 8) async -> (Double, Double)? {
await withCheckedContinuation { continuation in await withCheckedContinuation { continuation in
var hasResumed = false var hasResumed = false

View File

@@ -349,8 +349,29 @@ struct CartView: View {
SessionStateStore.saveAddress(appState.address) SessionStateStore.saveAddress(appState.address)
} }
let payloadLat = appState.address.latitude ?? selectedCustomerAddress?.latLong?.first var payloadLat = appState.address.latitude ?? selectedCustomerAddress?.latLong?.first
let payloadLng = appState.address.longitude ?? selectedCustomerAddress?.latLong?.dropFirst().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( let payload = ValidateDeliveryAddressPayload(
address: ValidateDeliveryAddressDataPayload( address: ValidateDeliveryAddressDataPayload(

View File

@@ -164,6 +164,25 @@ extension CheckoutView {
baseDeliveryFee = nil 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( let payload = ValidateDeliveryAddressPayload(
address: ValidateDeliveryAddressDataPayload( address: ValidateDeliveryAddressDataPayload(
street: selectedCustomerAddress?.address, street: selectedCustomerAddress?.address,