Browse Source

feat(shoprenter): add auto-registration with shop email detection

- Add fetchShopSettingsDirect() to fetch config_email and config_owner from ShopRenter Settings API
- Update oauth-shoprenter-callback to store shop_email and shop_owner_name in pending_shoprenter_installs
- Create get-pending-install-info endpoint to return shop info and check if email exists
- Create auto-register-shoprenter endpoint for one-click account creation with auto-login
- Update IntegrationsRedirect.tsx with enhanced UI showing detected email and quick start option
- Add i18n translations for en, hu, de languages
- Add database migration for shop_email and shop_owner_name columns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh 4 months ago
parent
commit
79ad60b6de

+ 48 - 3
shopcall.ai-main/src/i18n/locales/de.json

@@ -252,7 +252,12 @@
     "view": "Ansehen",
     "view": "Ansehen",
     "details": "Details",
     "details": "Details",
     "refresh": "Aktualisieren",
     "refresh": "Aktualisieren",
-    "tryAgain": "Erneut Versuchen"
+    "tryAgain": "Erneut Versuchen",
+    "personalData": "Persönliche Daten",
+    "justNow": "gerade eben",
+    "minsAgo": "vor {{count}} Min.",
+    "hoursAgo": "vor {{count}} Std.",
+    "daysAgo": "vor {{count}} Tagen"
   },
   },
   "signup": {
   "signup": {
     "title": "Registrieren",
     "title": "Registrieren",
@@ -342,6 +347,8 @@
     "backToList": "Zurück zur Anrufliste",
     "backToList": "Zurück zur Anrufliste",
     "notFound": "Anruf nicht gefunden",
     "notFound": "Anruf nicht gefunden",
     "callInfo": "Anrufinformationen",
     "callInfo": "Anrufinformationen",
+    "contactInfo": "Kontaktinformationen",
+    "callHistory": "Anrufverlauf",
     "startedAt": "Begonnen",
     "startedAt": "Begonnen",
     "endedAt": "Beendet",
     "endedAt": "Beendet",
     "caller": "Anrufer",
     "caller": "Anrufer",
@@ -352,7 +359,31 @@
     "costsBreakdown": "Kostenaufschlüsselung",
     "costsBreakdown": "Kostenaufschlüsselung",
     "total": "Gesamt",
     "total": "Gesamt",
     "transcript": "Transkript",
     "transcript": "Transkript",
-    "noTranscript": "Kein Transkript verfügbar"
+    "noTranscript": "Kein Transkript verfügbar",
+    "totalCalls": "Anrufe Gesamt",
+    "totalDuration": "Gesamtdauer",
+    "totalCost": "Gesamtkosten",
+    "callOutcome": "Anrufergebnis",
+    "summary": "Zusammenfassung",
+    "addedAgo": "Hinzugefügt {{time}}",
+    "relatedCustomers": "Zugehörige Kunden",
+    "relatedOrders": "Zugehörige Bestellungen",
+    "relatedProducts": "Zugehörige Produkte"
+  },
+  "outcomes": {
+    "order_inquiry": "Bestellanfrage",
+    "product_inquiry": "Produktanfrage",
+    "order_placed": "Bestellung Aufgegeben",
+    "complaint": "Beschwerde",
+    "return_request": "Rückgabeanfrage",
+    "general_question": "Allgemeine Frage",
+    "callback_requested": "Rückruf Angefordert",
+    "no_answer": "Keine Antwort",
+    "voicemail": "Voicemail",
+    "wrong_number": "Falsche Nummer",
+    "resolved": "Gelöst",
+    "escalated": "Eskaliert",
+    "abandoned": "Abgebrochen"
   },
   },
   "analytics": {
   "analytics": {
     "title": "Analysen",
     "title": "Analysen",
@@ -809,7 +840,21 @@
       "processing": "Integration wird verarbeitet...",
       "processing": "Integration wird verarbeitet...",
       "backToIntegrations": "Zurück zu Integrationen",
       "backToIntegrations": "Zurück zu Integrationen",
       "shopConnected": "Shop Verbunden",
       "shopConnected": "Shop Verbunden",
-      "shopConnectedDescription": "{{shopName}} erfolgreich verbunden"
+      "shopConnectedDescription": "{{shopName}} erfolgreich verbunden",
+      "loadingShopInfo": "Shop-Informationen werden geladen...",
+      "detectedEmail": "Wir haben Ihre Shop-E-Mail erkannt",
+      "useDetectedEmail": "Schnellstart mit {{email}}",
+      "creatingAccount": "Konto wird erstellt...",
+      "registerDifferentEmail": "Mit anderer E-Mail registrieren",
+      "emailAlreadyRegisteredHint": "Diese E-Mail ist bereits registriert. Bitte melden Sie sich an.",
+      "loginWithEmail": "Anmelden als {{email}}",
+      "emailAlreadyExists": "E-Mail bereits registriert",
+      "pleaseLogin": "Bitte melden Sie sich mit Ihrem bestehenden Konto an.",
+      "autoRegisterSuccess": "Konto erfolgreich erstellt!",
+      "autoRegisterPasswordHint": "Für zukünftige Anmeldungen nutzen Sie bitte die Passwort-Zurücksetzen-Option.",
+      "registrationFailed": "Registrierung fehlgeschlagen",
+      "emailMismatch": "Ihre angemeldete E-Mail ({{userEmail}}) unterscheidet sich von der Shop-E-Mail ({{shopEmail}}).",
+      "switchToShopEmail": "Zu {{email}} wechseln"
     },
     },
     "shoprenter": {
     "shoprenter": {
       "seo": {
       "seo": {

+ 47 - 3
shopcall.ai-main/src/i18n/locales/en.json

@@ -253,7 +253,11 @@
     "details": "Details",
     "details": "Details",
     "refresh": "Refresh",
     "refresh": "Refresh",
     "tryAgain": "Try Again",
     "tryAgain": "Try Again",
-    "personalData": "Personal data"
+    "personalData": "Personal data",
+    "justNow": "just now",
+    "minsAgo": "{{count}} mins ago",
+    "hoursAgo": "{{count}} hours ago",
+    "daysAgo": "{{count}} days ago"
   },
   },
   "signup": {
   "signup": {
     "title": "Sign Up",
     "title": "Sign Up",
@@ -345,6 +349,8 @@
     "backToList": "Back to Call Logs",
     "backToList": "Back to Call Logs",
     "notFound": "Call not found",
     "notFound": "Call not found",
     "callInfo": "Call Information",
     "callInfo": "Call Information",
+    "contactInfo": "Contact Information",
+    "callHistory": "Call History",
     "startedAt": "Started At",
     "startedAt": "Started At",
     "endedAt": "Ended At",
     "endedAt": "Ended At",
     "caller": "Caller",
     "caller": "Caller",
@@ -355,7 +361,31 @@
     "costsBreakdown": "Costs Breakdown",
     "costsBreakdown": "Costs Breakdown",
     "total": "Total",
     "total": "Total",
     "transcript": "Transcript",
     "transcript": "Transcript",
-    "noTranscript": "No transcript available"
+    "noTranscript": "No transcript available",
+    "totalCalls": "Total Calls",
+    "totalDuration": "Total Duration",
+    "totalCost": "Total Cost",
+    "callOutcome": "Call Outcome",
+    "summary": "Summary",
+    "addedAgo": "Added {{time}}",
+    "relatedCustomers": "Related Customers",
+    "relatedOrders": "Related Orders",
+    "relatedProducts": "Related Products"
+  },
+  "outcomes": {
+    "order_inquiry": "Order Inquiry",
+    "product_inquiry": "Product Inquiry",
+    "order_placed": "Order Placed",
+    "complaint": "Complaint",
+    "return_request": "Return Request",
+    "general_question": "General Question",
+    "callback_requested": "Callback Requested",
+    "no_answer": "No Answer",
+    "voicemail": "Voicemail",
+    "wrong_number": "Wrong Number",
+    "resolved": "Resolved",
+    "escalated": "Escalated",
+    "abandoned": "Abandoned"
   },
   },
   "analytics": {
   "analytics": {
     "title": "Analytics",
     "title": "Analytics",
@@ -1010,7 +1040,21 @@
       "processing": "Processing integration...",
       "processing": "Processing integration...",
       "backToIntegrations": "Back to Integrations",
       "backToIntegrations": "Back to Integrations",
       "shopConnected": "Shop Connected",
       "shopConnected": "Shop Connected",
-      "shopConnectedDescription": "Successfully connected {{shopName}}"
+      "shopConnectedDescription": "Successfully connected {{shopName}}",
+      "loadingShopInfo": "Loading shop information...",
+      "detectedEmail": "We detected your shop email",
+      "useDetectedEmail": "Quick start with {{email}}",
+      "creatingAccount": "Creating account...",
+      "registerDifferentEmail": "Register with different email",
+      "emailAlreadyRegisteredHint": "This email is already registered. Please login.",
+      "loginWithEmail": "Login as {{email}}",
+      "emailAlreadyExists": "Email already registered",
+      "pleaseLogin": "Please login with your existing account.",
+      "autoRegisterSuccess": "Account created successfully!",
+      "autoRegisterPasswordHint": "For future logins, please use the password reset option.",
+      "registrationFailed": "Registration Failed",
+      "emailMismatch": "Your logged-in email ({{userEmail}}) differs from the shop email ({{shopEmail}}).",
+      "switchToShopEmail": "Switch to {{email}} instead"
     },
     },
     "shoprenter": {
     "shoprenter": {
       "seo": {
       "seo": {

+ 47 - 3
shopcall.ai-main/src/i18n/locales/hu.json

@@ -253,7 +253,11 @@
     "details": "Részletek",
     "details": "Részletek",
     "refresh": "Frissítés",
     "refresh": "Frissítés",
     "tryAgain": "Próbálja újra",
     "tryAgain": "Próbálja újra",
-    "personalData": "Személyes adatok"
+    "personalData": "Személyes adatok",
+    "justNow": "épp most",
+    "minsAgo": "{{count}} perce",
+    "hoursAgo": "{{count}} órája",
+    "daysAgo": "{{count}} napja"
   },
   },
   "signup": {
   "signup": {
     "title": "Regisztráció",
     "title": "Regisztráció",
@@ -345,6 +349,8 @@
     "backToList": "Vissza a Hívásnaplóhoz",
     "backToList": "Vissza a Hívásnaplóhoz",
     "notFound": "Hívás nem található",
     "notFound": "Hívás nem található",
     "callInfo": "Hívás Információk",
     "callInfo": "Hívás Információk",
+    "contactInfo": "Kapcsolat Információk",
+    "callHistory": "Hívás Előzmények",
     "startedAt": "Kezdés",
     "startedAt": "Kezdés",
     "endedAt": "Befejezés",
     "endedAt": "Befejezés",
     "caller": "Hívó",
     "caller": "Hívó",
@@ -355,7 +361,31 @@
     "costsBreakdown": "Költségek Bontása",
     "costsBreakdown": "Költségek Bontása",
     "total": "Összesen",
     "total": "Összesen",
     "transcript": "Átirat",
     "transcript": "Átirat",
-    "noTranscript": "Nincs elérhető átirat"
+    "noTranscript": "Nincs elérhető átirat",
+    "totalCalls": "Összes Hívás",
+    "totalDuration": "Összes Időtartam",
+    "totalCost": "Összes Költség",
+    "callOutcome": "Hívás Eredménye",
+    "summary": "Összefoglaló",
+    "addedAgo": "Hozzáadva {{time}}",
+    "relatedCustomers": "Kapcsolódó Ügyfelek",
+    "relatedOrders": "Kapcsolódó Rendelések",
+    "relatedProducts": "Kapcsolódó Termékek"
+  },
+  "outcomes": {
+    "order_inquiry": "Rendelés Érdeklődés",
+    "product_inquiry": "Termék Érdeklődés",
+    "order_placed": "Rendelés Leadva",
+    "complaint": "Panasz",
+    "return_request": "Visszaküldési Kérelem",
+    "general_question": "Általános Kérdés",
+    "callback_requested": "Visszahívás Kérés",
+    "no_answer": "Nincs Válasz",
+    "voicemail": "Hangposta",
+    "wrong_number": "Téves Szám",
+    "resolved": "Megoldva",
+    "escalated": "Továbbított",
+    "abandoned": "Félbeszakadt"
   },
   },
   "analytics": {
   "analytics": {
     "title": "Elemzések",
     "title": "Elemzések",
@@ -888,7 +918,21 @@
       "processing": "Integráció feldolgozása...",
       "processing": "Integráció feldolgozása...",
       "backToIntegrations": "Vissza az Integrációkhoz",
       "backToIntegrations": "Vissza az Integrációkhoz",
       "shopConnected": "Áruház Csatlakoztatva",
       "shopConnected": "Áruház Csatlakoztatva",
-      "shopConnectedDescription": "{{shopName}} sikeresen csatlakoztatva"
+      "shopConnectedDescription": "{{shopName}} sikeresen csatlakoztatva",
+      "loadingShopInfo": "Áruház információk betöltése...",
+      "detectedEmail": "Felismertük az áruház email címét",
+      "useDetectedEmail": "Gyors kezdés: {{email}}",
+      "creatingAccount": "Fiók létrehozása...",
+      "registerDifferentEmail": "Regisztráció más email címmel",
+      "emailAlreadyRegisteredHint": "Ez az email cím már regisztrálva van. Kérjük, jelentkezzen be.",
+      "loginWithEmail": "Bejelentkezés: {{email}}",
+      "emailAlreadyExists": "Email cím már regisztrálva",
+      "pleaseLogin": "Kérjük, jelentkezzen be a meglévő fiókjával.",
+      "autoRegisterSuccess": "Fiók sikeresen létrehozva!",
+      "autoRegisterPasswordHint": "A jövőbeni bejelentkezéshez használja a jelszó-visszaállítás opciót.",
+      "registrationFailed": "Regisztráció sikertelen",
+      "emailMismatch": "A bejelentkezett email címe ({{userEmail}}) eltér az áruház email címétől ({{shopEmail}}).",
+      "switchToShopEmail": "Váltás erre: {{email}}"
     },
     },
     "shoprenter": {
     "shoprenter": {
       "seo": {
       "seo": {

+ 199 - 19
shopcall.ai-main/src/pages/IntegrationsRedirect.tsx

@@ -3,13 +3,14 @@ import { useNavigate, useSearchParams } from "react-router-dom";
 import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
 import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
 import { Button } from "@/components/ui/button";
 import { Button } from "@/components/ui/button";
 import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
 import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
-import { Store, LogIn, UserPlus, Loader2, CheckCircle, AlertCircle, ArrowRight, Phone } from "lucide-react";
+import { Store, LogIn, UserPlus, Loader2, CheckCircle, AlertCircle, ArrowRight, Phone, Link, Mail, Sparkles } from "lucide-react";
 import { useAuth } from "@/components/context/AuthContext";
 import { useAuth } from "@/components/context/AuthContext";
 import { API_URL } from "@/lib/config";
 import { API_URL } from "@/lib/config";
 import { useToast } from "@/hooks/use-toast";
 import { useToast } from "@/hooks/use-toast";
 import { useTranslation } from "react-i18next";
 import { useTranslation } from "react-i18next";
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 import { Label } from "@/components/ui/label";
 import { Label } from "@/components/ui/label";
+import { LoadingScreen } from "@/components/ui/loading-screen";
 
 
 interface PendingInstallation {
 interface PendingInstallation {
   installation_id: string;
   installation_id: string;
@@ -33,6 +34,12 @@ interface Country {
   country_name: string;
   country_name: string;
 }
 }
 
 
+interface ShopInfo {
+  shop_email?: string;
+  shop_owner_name?: string;
+  email_exists?: boolean;
+}
+
 export default function IntegrationsRedirect() {
 export default function IntegrationsRedirect() {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const navigate = useNavigate();
   const navigate = useNavigate();
@@ -60,6 +67,12 @@ export default function IntegrationsRedirect() {
   const [loadingPhoneNumbers, setLoadingPhoneNumbers] = useState(false);
   const [loadingPhoneNumbers, setLoadingPhoneNumbers] = useState(false);
   const [userCountry, setUserCountry] = useState<string | null>(null);
   const [userCountry, setUserCountry] = useState<string | null>(null);
 
 
+  // Shop info for auto-registration
+  const [shopInfo, setShopInfo] = useState<ShopInfo | null>(null);
+  const [loadingShopInfo, setLoadingShopInfo] = useState(false);
+  const [autoRegistering, setAutoRegistering] = useState(false);
+  const [userEmail, setUserEmail] = useState<string | null>(null);
+
   // Check authentication status on mount
   // Check authentication status on mount
   useEffect(() => {
   useEffect(() => {
     const checkAuthStatus = async () => {
     const checkAuthStatus = async () => {
@@ -91,6 +104,10 @@ export default function IntegrationsRedirect() {
 
 
         const data = await response.json();
         const data = await response.json();
         setIsAuthenticated(data.success === true);
         setIsAuthenticated(data.success === true);
+        // Store user email for comparison with shop email
+        if (data.user?.email) {
+          setUserEmail(data.user.email);
+        }
       } catch (err) {
       } catch (err) {
         console.error('Auth check failed:', err);
         console.error('Auth check failed:', err);
         setIsAuthenticated(false);
         setIsAuthenticated(false);
@@ -177,6 +194,36 @@ export default function IntegrationsRedirect() {
           shopname: shopname,
           shopname: shopname,
           platform: 'shoprenter'
           platform: 'shoprenter'
         });
         });
+
+        // Fetch shop info for auto-registration feature
+        setLoadingShopInfo(true);
+        try {
+          const infoResponse = await fetch(`${API_URL}/get-pending-install-info`, {
+            method: 'POST',
+            headers: {
+              'Content-Type': 'application/json'
+            },
+            body: JSON.stringify({ installation_id: srInstall })
+          });
+
+          if (infoResponse.ok) {
+            const shopInfoData = await infoResponse.json();
+            if (shopInfoData.success) {
+              setShopInfo({
+                shop_email: shopInfoData.shop_email,
+                shop_owner_name: shopInfoData.shop_owner_name,
+                email_exists: shopInfoData.email_exists
+              });
+              console.log('[ShopRenter] Shop info fetched:', shopInfoData);
+            }
+          } else {
+            console.warn('[ShopRenter] Failed to fetch shop info');
+          }
+        } catch (infoErr) {
+          console.error('[ShopRenter] Error fetching shop info:', infoErr);
+        } finally {
+          setLoadingShopInfo(false);
+        }
       }
       }
     };
     };
 
 
@@ -478,6 +525,79 @@ export default function IntegrationsRedirect() {
     navigate('/signup');
     navigate('/signup');
   };
   };
 
 
+  // Handle auto-registration with shop email
+  const handleAutoRegister = async () => {
+    if (!pendingInstall || !shopInfo?.shop_email) return;
+
+    setAutoRegistering(true);
+    try {
+      const response = await fetch(`${API_URL}/auto-register-shoprenter`, {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+          installation_id: pendingInstall.installation_id,
+          phone_number_id: selectedPhoneNumber || null
+        })
+      });
+
+      const data = await response.json();
+
+      if (!response.ok || !data.success) {
+        // If email already exists, prompt user to login
+        if (data.email_exists) {
+          toast({
+            title: t('integrations.oauth.emailAlreadyExists', 'Email already registered'),
+            description: t('integrations.oauth.pleaseLogin', 'Please login with your existing account.'),
+            variant: "destructive",
+          });
+          handleLogin();
+          return;
+        }
+        throw new Error(data.error || 'Auto-registration failed');
+      }
+
+      // Store the session for auto-login
+      localStorage.setItem('session_data', JSON.stringify({
+        success: true,
+        session: data.session
+      }));
+
+      // Show success toast with password reset hint
+      toast({
+        title: t('integrations.oauth.autoRegisterSuccess', 'Account created successfully!'),
+        description: t('integrations.oauth.autoRegisterPasswordHint', 'For future logins, please use the password reset option.'),
+      });
+
+      // Navigate to success page
+      navigate('/webshops?sr_connected=true&store=' + encodeURIComponent(pendingInstall.shopname));
+
+    } catch (err) {
+      console.error('Auto-registration error:', err);
+      const errorMessage = err instanceof Error ? err.message : 'Auto-registration failed';
+      setError(errorMessage);
+      toast({
+        title: t('integrations.oauth.registrationFailed', 'Registration Failed'),
+        description: errorMessage,
+        variant: "destructive",
+      });
+    } finally {
+      setAutoRegistering(false);
+    }
+  };
+
+  // Handle switching to shop email (logout and auto-register)
+  const handleSwitchToShopEmail = () => {
+    // Log out current user
+    localStorage.removeItem('session_data');
+    setIsAuthenticated(false);
+    setUserEmail(null);
+    // Show auth dialog which will now show auto-register option
+    setShowAssignDialog(false);
+    setShowAuthDialog(true);
+  };
+
   // Show error state
   // Show error state
   if (error) {
   if (error) {
     return (
     return (
@@ -506,20 +626,20 @@ export default function IntegrationsRedirect() {
   }
   }
 
 
   // Show loading state
   // Show loading state
-  if (checkingAuth || validating || (!pendingInstall && !error)) {
+  if (checkingAuth || validating || loadingShopInfo || (!pendingInstall && !error)) {
+    const loadingMessage = validating
+      ? t('integrations.oauth.validating', 'Validating security parameters...')
+      : loadingShopInfo
+      ? t('integrations.oauth.loadingShopInfo', 'Loading shop information...')
+      : checkingAuth
+      ? t('integrations.oauth.checkingAuth', 'Checking authentication...')
+      : t('integrations.oauth.processing', 'Processing integration...');
+
     return (
     return (
-      <div className="min-h-screen flex items-center justify-center bg-slate-900">
-        <div className="text-center">
-          <Loader2 className="w-12 h-12 text-cyan-500 animate-spin mx-auto mb-4" />
-          <p className="text-slate-400">
-            {validating
-              ? t('integrations.oauth.validating', 'Validating security parameters...')
-              : checkingAuth
-              ? t('integrations.oauth.checkingAuth', 'Checking authentication...')
-              : t('integrations.oauth.processing', 'Processing integration...')}
-          </p>
-        </div>
-      </div>
+      <LoadingScreen
+        icon={Link}
+        message={loadingMessage}
+      />
     );
     );
   }
   }
 
 
@@ -557,27 +677,71 @@ export default function IntegrationsRedirect() {
               {t('integrations.oauth.connectShop', 'Connect Your Shop')}
               {t('integrations.oauth.connectShop', 'Connect Your Shop')}
             </DialogTitle>
             </DialogTitle>
             <DialogDescription className="text-slate-400 text-center">
             <DialogDescription className="text-slate-400 text-center">
-              {t('integrations.oauth.authRequired', 'Sign in or create an account to connect')}
-              <span className="font-semibold text-white"> {pendingInstall?.shopname}</span>
+              {shopInfo?.shop_email ? (
+                <>
+                  {t('integrations.oauth.detectedEmail', 'We detected your shop email')}
+                  <span className="font-semibold text-cyan-400 block mt-1">{shopInfo.shop_email}</span>
+                </>
+              ) : (
+                <>
+                  {t('integrations.oauth.authRequired', 'Sign in or create an account to connect')}
+                  <span className="font-semibold text-white"> {pendingInstall?.shopname}</span>
+                </>
+              )}
             </DialogDescription>
             </DialogDescription>
           </DialogHeader>
           </DialogHeader>
 
 
           <div className="space-y-3 mt-4">
           <div className="space-y-3 mt-4">
+            {/* Auto-register option when shop email is available and not already registered */}
+            {shopInfo?.shop_email && !shopInfo.email_exists && (
+              <Button
+                className="w-full bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-600 hover:to-blue-600 text-white"
+                onClick={handleAutoRegister}
+                disabled={autoRegistering}
+              >
+                {autoRegistering ? (
+                  <>
+                    <Loader2 className="w-4 h-4 mr-2 animate-spin" />
+                    {t('integrations.oauth.creatingAccount', 'Creating account...')}
+                  </>
+                ) : (
+                  <>
+                    <Sparkles className="w-4 h-4 mr-2" />
+                    {t('integrations.oauth.useDetectedEmail', 'Quick start with {{email}}', { email: shopInfo.shop_email })}
+                  </>
+                )}
+              </Button>
+            )}
+
+            {/* Login option when shop email already exists */}
+            {shopInfo?.shop_email && shopInfo.email_exists && (
+              <div className="bg-blue-500/10 border border-blue-500/30 rounded-md p-3 mb-2">
+                <p className="text-sm text-blue-400 flex items-center">
+                  <Mail className="w-4 h-4 mr-2" />
+                  {t('integrations.oauth.emailAlreadyRegisteredHint', 'This email is already registered. Please login.')}
+                </p>
+              </div>
+            )}
+
             <Button
             <Button
-              className="w-full bg-cyan-500 hover:bg-cyan-600 text-white"
+              className={`w-full ${shopInfo?.shop_email && !shopInfo.email_exists ? 'bg-slate-700 hover:bg-slate-600' : 'bg-cyan-500 hover:bg-cyan-600'} text-white`}
               onClick={handleLogin}
               onClick={handleLogin}
+              disabled={autoRegistering}
             >
             >
               <LogIn className="w-4 h-4 mr-2" />
               <LogIn className="w-4 h-4 mr-2" />
-              {t('integrations.oauth.signIn', 'Sign in to your account')}
+              {shopInfo?.shop_email && shopInfo.email_exists
+                ? t('integrations.oauth.loginWithEmail', 'Login as {{email}}', { email: shopInfo.shop_email })
+                : t('integrations.oauth.signIn', 'Sign in to your account')}
             </Button>
             </Button>
 
 
             <Button
             <Button
               variant="outline"
               variant="outline"
               className="w-full border-slate-600 text-white hover:bg-slate-700"
               className="w-full border-slate-600 text-white hover:bg-slate-700"
               onClick={handleSignup}
               onClick={handleSignup}
+              disabled={autoRegistering}
             >
             >
               <UserPlus className="w-4 h-4 mr-2" />
               <UserPlus className="w-4 h-4 mr-2" />
-              {t('integrations.oauth.createAccount', 'Create a new account')}
+              {t('integrations.oauth.registerDifferentEmail', 'Register with different email')}
             </Button>
             </Button>
           </div>
           </div>
 
 
@@ -601,6 +765,22 @@ export default function IntegrationsRedirect() {
             </DialogDescription>
             </DialogDescription>
           </DialogHeader>
           </DialogHeader>
 
 
+          {/* Show notice if logged-in email differs from shop email */}
+          {shopInfo?.shop_email && userEmail && shopInfo.shop_email !== userEmail && !shopInfo.email_exists && (
+            <div className="bg-amber-500/10 border border-amber-500/30 rounded-md p-3 mt-2">
+              <p className="text-sm text-amber-400">
+                {t('integrations.oauth.emailMismatch', 'Your logged-in email ({{userEmail}}) differs from the shop email ({{shopEmail}}).', { userEmail, shopEmail: shopInfo.shop_email })}
+              </p>
+              <Button
+                variant="link"
+                className="text-amber-400 hover:text-amber-300 p-0 h-auto mt-1 text-sm"
+                onClick={handleSwitchToShopEmail}
+              >
+                {t('integrations.oauth.switchToShopEmail', 'Switch to {{email}} instead', { email: shopInfo.shop_email })}
+              </Button>
+            </div>
+          )}
+
           {/* Phone Number Selection - Cascading: Country → City → Phone */}
           {/* Phone Number Selection - Cascading: Country → City → Phone */}
           <div className="mt-4 space-y-4">
           <div className="mt-4 space-y-4">
             {/* Country Selector */}
             {/* Country Selector */}

+ 83 - 0
supabase/functions/_shared/shoprenter-client.ts

@@ -273,6 +273,89 @@ export interface ShopRenterOrder {
   }
   }
 }
 }
 
 
+export interface ShopRenterSettings {
+  config_email?: string
+  config_owner?: string
+}
+
+/**
+ * Fetch shop settings directly using shopname and access token.
+ * This is used during OAuth flow before a store record exists.
+ * Endpoint: GET /settings?full=1&key=config_email,config_owner
+ */
+export async function fetchShopSettingsDirect(
+  shopname: string,
+  accessToken: string
+): Promise<ShopRenterSettings> {
+  const hostname = `${shopname}.api2.myshoprenter.hu`
+  const path = '/api/settings?full=1&key=config_email,config_owner'
+
+  console.log(`[ShopRenter] Fetching shop settings for ${shopname}`)
+
+  const requestHeaders: Record<string, string> = {
+    'Authorization': `Bearer ${accessToken}`,
+    'Content-Type': 'application/json',
+    'Accept': 'application/json'
+  }
+
+  try {
+    const response = await makeHttp10Request(hostname, path, 'GET', requestHeaders)
+
+    if (response.status !== 200) {
+      console.error(`[ShopRenter] Settings API error (${response.status}):`, response.body)
+      // Return empty settings on error - don't fail the OAuth flow
+      return {}
+    }
+
+    const data = JSON.parse(response.body)
+    console.log('[ShopRenter] Settings response:', JSON.stringify(data).substring(0, 500))
+
+    // Extract config_email and config_owner from the response
+    // ShopRenter settings API returns data in various formats, handle them
+    let settings: ShopRenterSettings = {}
+
+    if (Array.isArray(data)) {
+      // If array, find the settings by key
+      for (const item of data) {
+        if (item.key === 'config_email') {
+          settings.config_email = item.value
+        } else if (item.key === 'config_owner') {
+          settings.config_owner = item.value
+        }
+      }
+    } else if (data.items && Array.isArray(data.items)) {
+      // If paginated response with items array
+      for (const item of data.items) {
+        if (item.key === 'config_email') {
+          settings.config_email = item.value
+        } else if (item.key === 'config_owner') {
+          settings.config_owner = item.value
+        }
+      }
+    } else if (data.data && Array.isArray(data.data)) {
+      // Alternative paginated format
+      for (const item of data.data) {
+        if (item.key === 'config_email') {
+          settings.config_email = item.value
+        } else if (item.key === 'config_owner') {
+          settings.config_owner = item.value
+        }
+      }
+    } else if (typeof data === 'object') {
+      // Direct object format
+      settings.config_email = data.config_email || data.configEmail
+      settings.config_owner = data.config_owner || data.configOwner
+    }
+
+    console.log(`[ShopRenter] Extracted settings for ${shopname}:`, settings)
+    return settings
+  } catch (error) {
+    console.error('[ShopRenter] Error fetching settings:', error)
+    // Return empty settings on error - don't fail the OAuth flow
+    return {}
+  }
+}
+
 // Get valid access token (with automatic refresh or client_credentials)
 // Get valid access token (with automatic refresh or client_credentials)
 export async function getValidAccessToken(storeId: string): Promise<string> {
 export async function getValidAccessToken(storeId: string): Promise<string> {
   const supabaseUrl = Deno.env.get('SUPABASE_URL')!
   const supabaseUrl = Deno.env.get('SUPABASE_URL')!

+ 412 - 0
supabase/functions/auto-register-shoprenter/index.ts

@@ -0,0 +1,412 @@
+import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
+import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
+import { wrapHandler } from '../_shared/error-handler.ts'
+import { getCorsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts'
+import { createScraperClient } from '../_shared/scraper-client.ts'
+
+/**
+ * Auto-register a user account during ShopRenter OAuth flow.
+ * This creates a new user account using the shop's config_email,
+ * logs them in automatically, and completes the store connection.
+ *
+ * Input: { installation_id: string, phone_number_id?: string }
+ * Output: { success: boolean, session: object, user: object, store: object }
+ */
+
+// Generate a secure random password
+function generateRandomPassword(length: number = 32): string {
+  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
+  const randomBytes = new Uint8Array(length)
+  crypto.getRandomValues(randomBytes)
+  return Array.from(randomBytes, byte => chars[byte % chars.length]).join('')
+}
+
+// Trigger initial data sync for the store
+async function triggerInitialSync(
+  storeId: string,
+  supabaseUrl: string,
+  supabaseServiceKey: string
+): Promise<void> {
+  try {
+    console.log(`[AutoRegister] Triggering initial sync for store ${storeId}`)
+
+    const response = await fetch(`${supabaseUrl}/functions/v1/trigger-sync`, {
+      method: 'POST',
+      headers: {
+        'Authorization': `Bearer ${supabaseServiceKey}`,
+        'Content-Type': 'application/json'
+      },
+      body: JSON.stringify({ store_id: storeId })
+    })
+
+    if (response.ok) {
+      const result = await response.json()
+      console.log(`[AutoRegister] Initial sync triggered successfully for store ${storeId}:`, result)
+    } else {
+      const error = await response.json()
+      console.error(`[AutoRegister] Failed to trigger initial sync for store ${storeId}:`, error)
+    }
+  } catch (error) {
+    console.error(`[AutoRegister] Error triggering initial sync for store ${storeId}:`, error)
+  }
+}
+
+// Register store with scraper service
+async function registerStoreWithScraper(
+  storeId: string,
+  storeUrl: string,
+  supabase: any
+): Promise<void> {
+  try {
+    console.log(`[AutoRegister] Registering store ${storeId} with scraper service`)
+
+    const scraperClient = await createScraperClient()
+    const job = await scraperClient.registerShop(storeUrl, storeId)
+    console.log(`[AutoRegister] Scraper registration job created: ${job.id}`)
+
+    const webhookUrl = `${Deno.env.get('SUPABASE_URL')}/functions/v1/scraper-webhook`
+    try {
+      await scraperClient.setWebhook(storeId, webhookUrl)
+      console.log(`[AutoRegister] Scraper webhook configured for store ${storeId}`)
+    } catch (webhookError) {
+      console.warn(`[AutoRegister] Failed to configure scraper webhook:`, webhookError)
+    }
+
+    try {
+      await scraperClient.setScheduling(storeId, true)
+      console.log(`[AutoRegister] Scraper scheduling enabled for store ${storeId}`)
+    } catch (scheduleError) {
+      console.warn(`[AutoRegister] Failed to enable scraper scheduling:`, scheduleError)
+    }
+
+    const { error: updateError } = await supabase
+      .from('stores')
+      .update({ scraper_registered: true })
+      .eq('id', storeId)
+
+    if (updateError) {
+      console.error(`[AutoRegister] Failed to update scraper registration status:`, updateError)
+    } else {
+      console.log(`[AutoRegister] Store ${storeId} successfully registered with scraper`)
+    }
+
+  } catch (error) {
+    console.error(`[AutoRegister] Failed to register store with scraper:`, error)
+  }
+}
+
+serve(wrapHandler('auto-register-shoprenter', async (req) => {
+  const origin = req.headers.get('Origin') || undefined
+  const corsHeaders = getCorsHeaders(origin)
+
+  if (req.method === 'OPTIONS') {
+    return handleCorsPreflightRequest(origin)
+  }
+
+  if (req.method !== 'POST') {
+    return new Response(
+      JSON.stringify({ success: false, error: 'Method not allowed' }),
+      { status: 405, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+    )
+  }
+
+  try {
+    const { installation_id, phone_number_id } = await req.json()
+
+    if (!installation_id) {
+      return new Response(
+        JSON.stringify({ success: false, error: 'Missing installation_id' }),
+        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    const supabaseUrl = Deno.env.get('SUPABASE_URL')!
+    const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
+    const supabase = createClient(supabaseUrl, supabaseServiceKey)
+
+    // Fetch the pending installation
+    const { data: pendingInstall, error: fetchError } = await supabase
+      .from('pending_shoprenter_installs')
+      .select('*')
+      .eq('installation_id', installation_id)
+      .single()
+
+    if (fetchError || !pendingInstall) {
+      console.error('[AutoRegister] Installation not found:', fetchError)
+      return new Response(
+        JSON.stringify({ success: false, error: 'Installation not found or expired' }),
+        { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    // Check if installation has expired
+    if (new Date(pendingInstall.expires_at) < new Date()) {
+      console.error('[AutoRegister] Installation has expired')
+      await supabase
+        .from('pending_shoprenter_installs')
+        .delete()
+        .eq('id', pendingInstall.id)
+
+      return new Response(
+        JSON.stringify({ success: false, error: 'Installation has expired. Please try again.' }),
+        { status: 410, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    // Check if we have shop_email
+    if (!pendingInstall.shop_email) {
+      return new Response(
+        JSON.stringify({ success: false, error: 'No shop email available for auto-registration. Please use manual registration.' }),
+        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    const email = pendingInstall.shop_email
+    const ownerName = pendingInstall.shop_owner_name || pendingInstall.shopname
+
+    console.log(`[AutoRegister] Processing auto-registration for ${email} (shop: ${pendingInstall.shopname})`)
+
+    // Check if email already exists in profiles
+    const { data: existingProfile } = await supabase
+      .from('profiles')
+      .select('id')
+      .eq('email', email)
+      .maybeSingle()
+
+    if (existingProfile) {
+      return new Response(
+        JSON.stringify({
+          success: false,
+          error: 'Email already registered. Please login instead.',
+          email_exists: true
+        }),
+        { status: 409, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    // Generate a secure random password (user won't know it, needs password reset for future logins)
+    const password = generateRandomPassword(32)
+
+    // Create user using Supabase Auth Admin API
+    console.log(`[AutoRegister] Creating user account for ${email}`)
+    const { data: authData, error: createUserError } = await supabase.auth.admin.createUser({
+      email: email,
+      password: password,
+      email_confirm: true, // Skip email verification - trust ShopRenter's email
+      user_metadata: {
+        full_name: ownerName,
+        company_name: ownerName,
+        user_name: ownerName.toLowerCase().replace(/\s+/g, '_'),
+        auto_registered: true,
+        auto_registered_from: 'shoprenter',
+        shopname: pendingInstall.shopname
+      }
+    })
+
+    if (createUserError || !authData.user) {
+      console.error('[AutoRegister] Failed to create user:', createUserError)
+      return new Response(
+        JSON.stringify({ success: false, error: 'Failed to create user account', details: createUserError?.message }),
+        { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    const newUser = authData.user
+    console.log(`[AutoRegister] User created: ${newUser.id}`)
+
+    // Create profile record
+    const { error: profileError } = await supabase
+      .from('profiles')
+      .upsert({
+        id: newUser.id,
+        email: email,
+        full_name: ownerName,
+        company_name: ownerName,
+        username: ownerName.toLowerCase().replace(/\s+/g, '_'),
+        is_verified: true,
+        created_at: new Date().toISOString(),
+        updated_at: new Date().toISOString()
+      })
+
+    if (profileError) {
+      console.error('[AutoRegister] Failed to create profile:', profileError)
+      // Continue anyway - profile might be auto-created by trigger
+    }
+
+    // Sign in the new user to get a session
+    console.log(`[AutoRegister] Signing in user ${email}`)
+    const { data: signInData, error: signInError } = await supabase.auth.signInWithPassword({
+      email: email,
+      password: password
+    })
+
+    if (signInError || !signInData.session) {
+      console.error('[AutoRegister] Failed to sign in new user:', signInError)
+      return new Response(
+        JSON.stringify({ success: false, error: 'Account created but failed to sign in. Please use password reset.', user_created: true }),
+        { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    const session = signInData.session
+    console.log(`[AutoRegister] User signed in successfully`)
+
+    // Now create the store (same logic as complete-shoprenter-install)
+    const shoprenterClientId = Deno.env.get('SHOPRENTER_APP_CLIENT_ID') || Deno.env.get('SHOPRENTER_CLIENT_ID')
+    const shoprenterClientSecret = Deno.env.get('SHOPRENTER_APP_CLIENT_SECRET') || Deno.env.get('SHOPRENTER_CLIENT_SECRET')
+
+    const finalPhoneNumberId = phone_number_id || pendingInstall.phone_number_id
+
+    // Check if store already exists for this user
+    const { data: existingStore } = await supabase
+      .from('stores')
+      .select('id')
+      .eq('user_id', newUser.id)
+      .eq('platform_name', 'shoprenter')
+      .eq('store_name', pendingInstall.shopname)
+      .single()
+
+    let storeId: string
+
+    if (existingStore) {
+      // Update existing store
+      const { error: updateError } = await supabase
+        .from('stores')
+        .update({
+          access_token: pendingInstall.access_token,
+          refresh_token: pendingInstall.refresh_token,
+          token_expires_at: new Date(Date.now() + (pendingInstall.expires_in || 3600) * 1000).toISOString(),
+          scopes: pendingInstall.scopes,
+          is_active: true,
+          updated_at: new Date().toISOString(),
+          phone_number_id: finalPhoneNumberId || existingStore.phone_number_id
+        })
+        .eq('id', existingStore.id)
+
+      if (updateError) {
+        console.error('[AutoRegister] Error updating existing store:', updateError)
+      }
+
+      storeId = existingStore.id
+      console.log(`[AutoRegister] Updated existing store ${storeId}`)
+    } else {
+      // Create new store
+      const { data: newStore, error: createError } = await supabase
+        .from('stores')
+        .insert({
+          user_id: newUser.id,
+          platform_name: 'shoprenter',
+          store_name: pendingInstall.shopname,
+          store_url: `https://${pendingInstall.shopname}.myshoprenter.hu`,
+          access_token: pendingInstall.access_token,
+          refresh_token: pendingInstall.refresh_token,
+          token_expires_at: new Date(Date.now() + (pendingInstall.expires_in || 3600) * 1000).toISOString(),
+          scopes: pendingInstall.scopes,
+          is_active: true,
+          phone_number_id: finalPhoneNumberId,
+          alt_data: {
+            client_id: shoprenterClientId,
+            client_secret: shoprenterClientSecret,
+            connectedAt: new Date().toISOString(),
+            autoRegistered: true
+          }
+        })
+        .select()
+        .single()
+
+      if (createError || !newStore) {
+        console.error('[AutoRegister] Error creating store:', createError)
+        return new Response(
+          JSON.stringify({ success: false, error: 'Failed to create store', session: session }),
+          { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
+      storeId = newStore.id
+      console.log(`[AutoRegister] Created new store ${storeId}`)
+
+      // Create default sync configuration
+      const { error: syncConfigError } = await supabase
+        .from('store_sync_config')
+        .insert({
+          store_id: storeId,
+          enabled: true,
+          sync_frequency: 'hourly',
+          products_access_policy: 'sync',
+          customers_access_policy: 'api_only',
+          orders_access_policy: 'api_only'
+        })
+
+      if (syncConfigError) {
+        console.error('[AutoRegister] Error creating sync config:', syncConfigError)
+      }
+    }
+
+    // Update phone number assignment if provided
+    if (finalPhoneNumberId) {
+      const { error: phoneError } = await supabase
+        .from('phone_numbers')
+        .update({
+          assigned_to_store_id: storeId,
+          assigned_to_user_id: newUser.id,
+          assigned_at: new Date().toISOString(),
+          is_available: false
+        })
+        .eq('id', finalPhoneNumberId)
+
+      if (phoneError) {
+        console.error('[AutoRegister] Error updating phone number:', phoneError)
+      }
+    }
+
+    // Clean up pending installation
+    await supabase
+      .from('pending_shoprenter_installs')
+      .delete()
+      .eq('id', pendingInstall.id)
+
+    // Clean up any related oauth_states
+    await supabase
+      .from('oauth_states')
+      .delete()
+      .eq('platform', 'shoprenter')
+      .eq('shopname', pendingInstall.shopname)
+
+    console.log(`[AutoRegister] Successfully completed auto-registration for ${email}`)
+
+    // Register store with scraper service in background
+    registerStoreWithScraper(storeId, `https://${pendingInstall.shopname}.myshoprenter.hu`, supabase)
+      .then(() => console.log(`[AutoRegister] Scraper registration completed for store ${storeId}`))
+      .catch(err => console.error(`[AutoRegister] Scraper registration failed:`, err))
+
+    // Trigger initial data sync in background
+    triggerInitialSync(storeId, supabaseUrl, supabaseServiceKey)
+      .then(() => console.log(`[AutoRegister] Initial sync completed for store ${storeId}`))
+      .catch(err => console.error(`[AutoRegister] Initial sync failed:`, err))
+
+    return new Response(
+      JSON.stringify({
+        success: true,
+        session: session,
+        user: {
+          id: newUser.id,
+          email: newUser.email,
+          full_name: ownerName
+        },
+        store: {
+          id: storeId,
+          shopname: pendingInstall.shopname
+        },
+        message: 'Account created and store connected successfully. For future logins, please use password reset.'
+      }),
+      { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+    )
+
+  } catch (error) {
+    console.error('[AutoRegister] Error:', error)
+    return new Response(
+      JSON.stringify({ success: false, error: 'Internal server error' }),
+      { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+    )
+  }
+}))

+ 108 - 0
supabase/functions/get-pending-install-info/index.ts

@@ -0,0 +1,108 @@
+import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
+import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
+import { wrapHandler } from '../_shared/error-handler.ts'
+
+const corsHeaders = {
+  'Access-Control-Allow-Origin': '*',
+  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
+}
+
+/**
+ * Get pending ShopRenter installation info including shop settings.
+ * This endpoint is public (no auth required) since the user may not be logged in yet.
+ *
+ * Input: { installation_id: string }
+ * Output: {
+ *   success: boolean,
+ *   shopname: string,
+ *   shop_email?: string,
+ *   shop_owner_name?: string,
+ *   email_exists: boolean // true if shop_email is already registered in profiles
+ * }
+ */
+serve(wrapHandler('get-pending-install-info', async (req) => {
+  if (req.method === 'OPTIONS') {
+    return new Response('ok', { headers: corsHeaders })
+  }
+
+  if (req.method !== 'POST') {
+    return new Response(
+      JSON.stringify({ error: 'Method not allowed' }),
+      { status: 405, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+    )
+  }
+
+  try {
+    const { installation_id } = await req.json()
+
+    if (!installation_id) {
+      return new Response(
+        JSON.stringify({ success: false, error: 'Missing installation_id' }),
+        { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    const supabaseUrl = Deno.env.get('SUPABASE_URL')!
+    const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
+    const supabase = createClient(supabaseUrl, supabaseServiceKey)
+
+    // Fetch the pending installation
+    const { data: pendingInstall, error: fetchError } = await supabase
+      .from('pending_shoprenter_installs')
+      .select('shopname, shop_email, shop_owner_name, expires_at')
+      .eq('installation_id', installation_id)
+      .single()
+
+    if (fetchError || !pendingInstall) {
+      console.error('[get-pending-install-info] Installation not found:', fetchError)
+      return new Response(
+        JSON.stringify({ success: false, error: 'Installation not found or expired' }),
+        { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    // Check if installation has expired
+    const expiresAt = new Date(pendingInstall.expires_at)
+    if (expiresAt < new Date()) {
+      console.log('[get-pending-install-info] Installation expired:', installation_id)
+      return new Response(
+        JSON.stringify({ success: false, error: 'Installation expired' }),
+        { status: 410, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
+    // Check if the shop_email already exists in profiles
+    let emailExists = false
+    if (pendingInstall.shop_email) {
+      const { data: existingProfile, error: profileError } = await supabase
+        .from('profiles')
+        .select('id')
+        .eq('email', pendingInstall.shop_email)
+        .maybeSingle()
+
+      if (!profileError && existingProfile) {
+        emailExists = true
+      }
+    }
+
+    console.log(`[get-pending-install-info] Returning info for ${pendingInstall.shopname}, email_exists: ${emailExists}`)
+
+    return new Response(
+      JSON.stringify({
+        success: true,
+        shopname: pendingInstall.shopname,
+        shop_email: pendingInstall.shop_email || null,
+        shop_owner_name: pendingInstall.shop_owner_name || null,
+        email_exists: emailExists
+      }),
+      { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+    )
+
+  } catch (error) {
+    console.error('[get-pending-install-info] Error:', error)
+    return new Response(
+      JSON.stringify({ success: false, error: 'Internal server error' }),
+      { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+    )
+  }
+}))

+ 15 - 2
supabase/functions/oauth-shoprenter-callback/index.ts

@@ -1,6 +1,7 @@
 import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
 import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
 import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
 import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
 import { wrapHandler, logError } from '../_shared/error-handler.ts'
 import { wrapHandler, logError } from '../_shared/error-handler.ts'
+import { fetchShopSettingsDirect } from '../_shared/shoprenter-client.ts'
 
 
 // Helper function to convert ArrayBuffer to hex string
 // Helper function to convert ArrayBuffer to hex string
 function bufferToHex(buffer: ArrayBuffer): string {
 function bufferToHex(buffer: ArrayBuffer): string {
@@ -409,6 +410,16 @@ serve(wrapHandler('oauth-shoprenter-callback', async (req) => {
     // Generate a unique installation ID
     // Generate a unique installation ID
     const installationId = crypto.randomUUID()
     const installationId = crypto.randomUUID()
 
 
+    // Fetch shop settings (config_email, config_owner) for auto-registration
+    let shopSettings = { config_email: undefined as string | undefined, config_owner: undefined as string | undefined }
+    try {
+      shopSettings = await fetchShopSettingsDirect(shopname, tokenData.access_token)
+      console.log(`[ShopRenter] Shop settings fetched for ${shopname}:`, shopSettings)
+    } catch (settingsError) {
+      // Don't fail the OAuth flow if settings fetch fails
+      console.error('[ShopRenter] Failed to fetch shop settings (continuing without):', settingsError)
+    }
+
     // Look up phone_number_id from oauth_states if available
     // Look up phone_number_id from oauth_states if available
     const { data: oauthState } = await supabase
     const { data: oauthState } = await supabase
       .from('oauth_states')
       .from('oauth_states')
@@ -425,7 +436,7 @@ serve(wrapHandler('oauth-shoprenter-callback', async (req) => {
     const expiresIn = tokenData.expires_in || 3600 // Default to 1 hour
     const expiresIn = tokenData.expires_in || 3600 // Default to 1 hour
     const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString()
     const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString()
 
 
-    // Store pending installation with tokens
+    // Store pending installation with tokens and shop settings
     const { error: insertError } = await supabase
     const { error: insertError } = await supabase
       .from('pending_shoprenter_installs')
       .from('pending_shoprenter_installs')
       .insert({
       .insert({
@@ -437,7 +448,9 @@ serve(wrapHandler('oauth-shoprenter-callback', async (req) => {
         expires_in: expiresIn,
         expires_in: expiresIn,
         scopes: tokenData.scope ? tokenData.scope.split(' ') : null,
         scopes: tokenData.scope ? tokenData.scope.split(' ') : null,
         expires_at: new Date(Date.now() + 15 * 60 * 1000).toISOString(), // 15 minutes to complete installation
         expires_at: new Date(Date.now() + 15 * 60 * 1000).toISOString(), // 15 minutes to complete installation
-        phone_number_id: phoneNumberId
+        phone_number_id: phoneNumberId,
+        shop_email: shopSettings.config_email || null,
+        shop_owner_name: shopSettings.config_owner || null
       })
       })
 
 
     if (insertError) {
     if (insertError) {

+ 11 - 0
supabase/migrations/20251201_add_shop_info_to_pending_installs.sql

@@ -0,0 +1,11 @@
+-- Add shop owner info columns to pending_shoprenter_installs
+-- These columns store the config_email and config_owner from ShopRenter settings API
+-- to enable auto-registration during the OAuth flow
+
+ALTER TABLE pending_shoprenter_installs
+ADD COLUMN IF NOT EXISTS shop_email text,
+ADD COLUMN IF NOT EXISTS shop_owner_name text;
+
+-- Add comment for documentation
+COMMENT ON COLUMN pending_shoprenter_installs.shop_email IS 'Shop admin email from ShopRenter config_email setting';
+COMMENT ON COLUMN pending_shoprenter_installs.shop_owner_name IS 'Shop owner name from ShopRenter config_owner setting';