Browse Source

fix: remove query parameters from WooCommerce OAuth callback URL #18

- WooCommerce OAuth spec requires callback URLs without query parameters
- Changed callback URL from https://...?action=callback to https://... (clean path)
- Detect callbacks via POST method + absence of action parameter
- This should fix the 'unable to send consumer data' error
Claude 5 months ago
parent
commit
af5e6d2
1 changed files with 129 additions and 138 deletions
  1. 129 138
      supabase/functions/oauth-woocommerce/index.ts

+ 129 - 138
supabase/functions/oauth-woocommerce/index.ts

@@ -125,7 +125,7 @@ serve(async (req) => {
 
   try {
     const url = new URL(req.url)
-    const action = url.searchParams.get('action') || 'init'
+    const action = url.searchParams.get('action')
 
     // Get environment variables
     const supabaseUrl = Deno.env.get('SUPABASE_URL')!
@@ -133,6 +133,130 @@ serve(async (req) => {
     const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
     const frontendUrl = Deno.env.get('FRONTEND_URL') || 'https://shopcall.ai'
 
+    // Detect WooCommerce OAuth callback: POST request without action parameter
+    // WooCommerce sends POST with form-urlencoded OAuth credentials
+    const isWooCommerceCallback = req.method === 'POST' && !action
+
+    // Handle OAuth callback from WooCommerce
+    if (isWooCommerceCallback) {
+      console.log('[WooCommerce] Detected OAuth callback via POST request')
+
+      // Parse form-urlencoded POST body
+      let success: string | null = null
+      let userId: string | null = null
+      let consumerKey: string | null = null
+      let consumerSecret: string | null = null
+      let storeUrlParam: string | null = null
+
+      const contentType = req.headers.get('content-type') || ''
+
+      if (contentType.includes('application/x-www-form-urlencoded')) {
+        const body = await req.text()
+        const params = new URLSearchParams(body)
+        success = params.get('success')
+        userId = params.get('user_id')
+        consumerKey = params.get('consumer_key')
+        consumerSecret = params.get('consumer_secret')
+        storeUrlParam = params.get('store_url')
+
+        console.log(`[WooCommerce] Callback params - success: ${success}, userId: ${userId}, hasKeys: ${!!consumerKey && !!consumerSecret}`)
+      } else if (contentType.includes('application/json')) {
+        const body = await req.json()
+        success = body.success
+        userId = body.user_id
+        consumerKey = body.consumer_key
+        consumerSecret = body.consumer_secret
+        storeUrlParam = body.store_url
+      }
+
+      if (success !== '1') {
+        console.error('[WooCommerce] OAuth rejected by user')
+        return new Response(
+          JSON.stringify({ success: false, message: 'OAuth rejected by user' }),
+          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
+      if (!userId || !consumerKey || !consumerSecret || !storeUrlParam) {
+        console.error('[WooCommerce] Missing required callback parameters')
+        return new Response(
+          JSON.stringify({ success: false, message: 'Missing required parameters' }),
+          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
+      // Validate store URL
+      const validation = validateStoreUrl(storeUrlParam)
+      if (!validation.valid) {
+        console.error('[WooCommerce] Invalid store URL:', validation.error)
+        return new Response(
+          JSON.stringify({ success: false, message: validation.error }),
+          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
+      // Test API connection
+      const testResult = await testWooCommerceConnection(
+        validation.normalized!,
+        consumerKey,
+        consumerSecret
+      )
+
+      if (!testResult.success) {
+        console.error('[WooCommerce] API connection test failed:', testResult.error)
+        return new Response(
+          JSON.stringify({ success: false, message: testResult.error }),
+          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
+      // Extract store info from API response
+      const systemStatus = testResult.data
+      const wcVersion = systemStatus?.environment?.version || 'unknown'
+      const wpVersion = systemStatus?.environment?.wp_version || 'unknown'
+      const storeName = systemStatus?.settings?.site_title?.value || new URL(validation.normalized!).hostname
+
+      // Create Supabase admin client
+      const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey)
+
+      // Store credentials in database
+      const { error: insertError } = await supabaseAdmin
+        .from('stores')
+        .insert({
+          user_id: userId,
+          platform_name: 'woocommerce',
+          store_name: storeName,
+          store_url: validation.normalized,
+          api_key: consumerKey,
+          api_secret: consumerSecret,
+          scopes: ['read'],
+          alt_data: {
+            wcVersion,
+            wpVersion,
+            apiVersion: 'wc/v3',
+            connectedAt: new Date().toISOString(),
+            authMethod: 'oauth'
+          }
+        })
+
+      if (insertError) {
+        console.error('[WooCommerce] Error storing credentials:', insertError)
+        return new Response(
+          JSON.stringify({ success: false, message: 'Failed to save credentials' }),
+          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+        )
+      }
+
+      console.log(`[WooCommerce] Store connected successfully: ${storeName}`)
+
+      // Return 200 OK with success message
+      // WooCommerce will redirect the user to the return_url
+      return new Response(
+        JSON.stringify({ success: true, message: 'Store connected successfully', storeName }),
+        { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+      )
+    }
+
     // Handle OAuth initiation
     if (action === 'init') {
       const storeUrl = url.searchParams.get('store_url')
@@ -199,8 +323,9 @@ serve(async (req) => {
       }
 
       // Build WooCommerce OAuth authorization URL
-      // Always use HTTPS for callback URL (WooCommerce requires SSL)
-      const callbackUrl = `https://${url.host}${url.pathname}?action=callback`
+      // IMPORTANT: Callback URL must NOT contain query parameters (WooCommerce requirement)
+      // We'll detect callbacks by checking POST method + WooCommerce OAuth parameters
+      const callbackUrl = `https://${url.host}${url.pathname}`
       const appName = 'ShopCall.ai'
       const returnUrl = `${frontendUrl}/webshops?wc_connected=true`
 
@@ -212,6 +337,7 @@ serve(async (req) => {
       authUrl.searchParams.set('callback_url', callbackUrl)
 
       console.log(`[WooCommerce] OAuth initiated for ${validation.normalized}`)
+      console.log(`[WooCommerce] Callback URL: ${callbackUrl}`)
 
       return new Response(
         JSON.stringify({
@@ -329,141 +455,6 @@ serve(async (req) => {
       )
     }
 
-    // Handle OAuth callback
-    if (action === 'callback') {
-      // WooCommerce sends OAuth credentials via POST request body
-      let success: string | null = null
-      let userId: string | null = null
-      let consumerKey: string | null = null
-      let consumerSecret: string | null = null
-      let storeUrlParam: string | null = null
-
-      // Parse POST body (WooCommerce sends form-urlencoded data)
-      if (req.method === 'POST') {
-        const contentType = req.headers.get('content-type') || ''
-
-        if (contentType.includes('application/x-www-form-urlencoded')) {
-          // Parse form-urlencoded body
-          const body = await req.text()
-          const params = new URLSearchParams(body)
-          success = params.get('success')
-          userId = params.get('user_id')
-          consumerKey = params.get('consumer_key')
-          consumerSecret = params.get('consumer_secret')
-          storeUrlParam = params.get('store_url')
-        } else if (contentType.includes('application/json')) {
-          // Fallback for JSON format
-          const body = await req.json()
-          success = body.success
-          userId = body.user_id
-          consumerKey = body.consumer_key
-          consumerSecret = body.consumer_secret
-          storeUrlParam = body.store_url
-        }
-      } else {
-        // Fallback to query parameters for GET requests
-        success = url.searchParams.get('success')
-        userId = url.searchParams.get('user_id')
-        consumerKey = url.searchParams.get('consumer_key')
-        consumerSecret = url.searchParams.get('consumer_secret')
-        storeUrlParam = url.searchParams.get('store_url')
-      }
-
-      console.log(`[WooCommerce] OAuth callback received - method: ${req.method}, success: ${success}, userId: ${userId}, hasKeys: ${!!consumerKey && !!consumerSecret}`)
-
-      if (success !== '1') {
-        console.error('[WooCommerce] OAuth rejected by user')
-        // Return 200 OK - WooCommerce handles the redirect to return_url
-        return new Response(
-          JSON.stringify({ success: false, message: 'OAuth rejected by user' }),
-          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
-        )
-      }
-
-      if (!userId || !consumerKey || !consumerSecret || !storeUrlParam) {
-        console.error('[WooCommerce] Missing required callback parameters')
-        // Return 200 OK - WooCommerce handles the redirect to return_url
-        return new Response(
-          JSON.stringify({ success: false, message: 'Missing required parameters' }),
-          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
-        )
-      }
-
-      // Validate store URL
-      const validation = validateStoreUrl(storeUrlParam)
-      if (!validation.valid) {
-        console.error('[WooCommerce] Invalid store URL:', validation.error)
-        // Return 200 OK - WooCommerce handles the redirect to return_url
-        return new Response(
-          JSON.stringify({ success: false, message: validation.error }),
-          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
-        )
-      }
-
-      // Test API connection
-      const testResult = await testWooCommerceConnection(
-        validation.normalized!,
-        consumerKey,
-        consumerSecret
-      )
-
-      if (!testResult.success) {
-        console.error('[WooCommerce] API connection test failed:', testResult.error)
-        // Return 200 OK - WooCommerce handles the redirect to return_url
-        return new Response(
-          JSON.stringify({ success: false, message: testResult.error }),
-          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
-        )
-      }
-
-      // Extract store info from API response
-      const systemStatus = testResult.data
-      const wcVersion = systemStatus?.environment?.version || 'unknown'
-      const wpVersion = systemStatus?.environment?.wp_version || 'unknown'
-      const storeName = systemStatus?.settings?.site_title?.value || new URL(validation.normalized!).hostname
-
-      // Create Supabase admin client
-      const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey)
-
-      // Store credentials in database
-      const { error: insertError } = await supabaseAdmin
-        .from('stores')
-        .insert({
-          user_id: userId,
-          platform_name: 'woocommerce',
-          store_name: storeName,
-          store_url: validation.normalized,
-          api_key: consumerKey,
-          api_secret: consumerSecret,
-          scopes: ['read'],
-          alt_data: {
-            wcVersion,
-            wpVersion,
-            apiVersion: 'wc/v3',
-            connectedAt: new Date().toISOString(),
-            authMethod: 'oauth'
-          }
-        })
-
-      if (insertError) {
-        console.error('[WooCommerce] Error storing credentials:', insertError)
-        // Return 200 OK - WooCommerce handles the redirect to return_url
-        return new Response(
-          JSON.stringify({ success: false, message: 'Failed to save credentials' }),
-          { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
-        )
-      }
-
-      console.log(`[WooCommerce] Store connected successfully: ${storeName}`)
-
-      // Return 200 OK with success message
-      // WooCommerce will redirect the user to the return_url we specified in the init request
-      return new Response(
-        JSON.stringify({ success: true, message: 'Store connected successfully', storeName }),
-        { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
-      )
-    }
-
     // Unknown action
     return new Response(
       JSON.stringify({ error: 'Invalid action parameter' }),