diff --git a/addons/dine360_uber/models/uber_config.py b/addons/dine360_uber/models/uber_config.py index 326f023..c60c6c3 100644 --- a/addons/dine360_uber/models/uber_config.py +++ b/addons/dine360_uber/models/uber_config.py @@ -110,10 +110,8 @@ class UberConfig(models.Model): return "https://api.uber.com/v1" def _get_token_url(self): - """Return OAuth token URL based on environment""" + """Return OAuth token URL (login.uber.com supports both Sandbox and Production)""" self.ensure_one() - if self.environment == 'sandbox': - return "https://sandbox-login.uber.com/oauth/v2/token" return "https://login.uber.com/oauth/v2/token" def _get_access_token(self): @@ -170,31 +168,63 @@ class UberConfig(models.Model): self.ensure_one() current_scope = (self.scope or '').strip() if not current_scope or current_scope != 'eats.deliveries': - # Default to required delivery scope self.write({'scope': 'eats.deliveries'}) try: token = self._get_access_token() - message = "Connection Successful! Token retrieved successfully using scope 'eats.deliveries'. Uber Direct delivery quotes and couriers are ready." - msg_type = "success" - return self._return_notification(message, msg_type) + message = "Connection Successful! Token retrieved successfully using scope 'eats.deliveries'. Uber Direct live delivery quotes and couriers are fully active." + return self._return_notification(message, "success") except UserError as e: err_str = str(e) + client_id = (self.client_id or '').strip() + client_secret = (self.client_secret or '').strip() + + # Diagnostic 1: Check if credentials work on Production login vs Sandbox login + org_token = None + detected_env = None + for env_name, token_url in [ + ('production', 'https://login.uber.com/oauth/v2/token'), + ('sandbox', 'https://sandbox-login.uber.com/oauth/v2/token') + ]: + try: + r = requests.post(token_url, data={ + 'client_id': client_id, + 'client_secret': client_secret, + 'grant_type': 'client_credentials', + 'scope': 'direct.organizations' + }, timeout=10) + if r.status_code == 200: + detected_env = env_name + org_token = r.json().get('access_token') + break + except Exception: + pass + + if detected_env: + env_mismatch_note = "" + if self.environment != detected_env: + env_mismatch_note = f"\n\nNote: Your credentials matched the {detected_env.upper()} environment, but Odoo is currently set to '{self.environment.capitalize()}'. Change Environment to '{detected_env.capitalize()}'." + + message = ( + f"Credentials Authenticated Successfully!\n\n" + f"Your Client ID and Secret are valid and connected to your Uber Direct organization (scope: 'direct.organizations').{env_mismatch_note}\n\n" + f"To enable 'eats.deliveries' (Courier Dispatch & Live Quotes):\n" + f"1. For Sandbox Testing: In direct.uber.com, click 'Switch to testing' (top right of Developer page), then copy the testing Client ID & Secret into Odoo and set Environment to 'Sandbox / Testing'.\n" + f"2. For Live Production: In direct.uber.com, click 'Set up' under Billing (left menu) to add a payment card/method and add your store address under Locations. Uber activates 'eats.deliveries' once billing is attached." + ) + return self._return_notification(message, "warning") + if "invalid_scope" in err_str.lower() or "scope" in err_str.lower(): message = ( "Uber Direct Permission Required: Your Uber Client ID requires the 'eats.deliveries' scope.\n\n" "How to resolve:\n" - "1. Go to https://developer.uber.com and log in.\n" - "2. Check that your app was created with the 'Uber Direct' / 'Deliveries' API Suite (not 'Others').\n" - "3. If created as 'Others', click 'Create Application' and choose 'Uber Direct' / 'Deliveries'.\n" - "4. Under 'Access Token' / 'Products', ensure 'eats.deliveries' is active." + "1. For Sandbox: In direct.uber.com, click 'Switch to testing' to use testing credentials.\n" + "2. For Production: Set up Billing and Location in direct.uber.com to activate live courier dispatch.\n" + "3. Ensure Environment in Odoo matches (Sandbox vs Production)." ) - msg_type = "warning" - else: - message = f"Connection Failed: {err_str}" - msg_type = "danger" + return self._return_notification(message, "warning") - return self._return_notification(message, msg_type) + return self._return_notification(f"Connection Failed: {err_str}", "danger") def _auth_with_scope(self, scope_to_test): """Helper to test a specific scope without saving""" @@ -460,11 +490,33 @@ class UberConfig(models.Model): 'raw_error': data } - # 2. Out of range / undeliverable error from Uber (Live coverage check) - if any(x in code.lower() or x in msg.lower() for x in ['out_of_range', 'out of range', 'outside', 'undeliverable', 'address_undeliverable', 'coverage', 'unserviceable']): + # 2. Tax form / Billing profile required + if 'tax_form' in msg.lower() or 'customer_blocked' in code.lower(): + tax_err = _( + "Uber Direct Account Setup Notice: Please complete your tax/billing details at " + "https://direct.uber.com/accounts/%s/billing to enable live deliveries." + ) % (customer_id or '') + if pricing_source == 'uber_fallback' and dropoff_data: + dist_result = self.calculate_distance_quote(dropoff_data, company) + dist_result['warning'] = tax_err + return dist_result return { 'success': False, - 'error': _("Uber Direct: This address is outside Uber's delivery coverage area. Please choose Store Pickup."), + 'error': tax_err, + 'code': code, + 'raw_error': data + } + + # 3. Out of range / undeliverable error from Uber (Live coverage check) + if any(x in code.lower() or x in msg.lower() for x in ['out_of_range', 'out of range', 'outside', 'undeliverable', 'address_undeliverable', 'coverage', 'unserviceable']): + details = data.get('metadata', {}).get('details') if isinstance(data.get('metadata'), dict) else '' + if details: + coverage_err = _("Uber Direct: %s Please select an address closer to the restaurant or choose Store Pickup.") % details + else: + coverage_err = _("Uber Direct: This address is outside Uber's delivery coverage area. Please choose Store Pickup.") + return { + 'success': False, + 'error': coverage_err, 'code': code, 'raw_error': data }