|
@@ -125,7 +125,7 @@ serve(async (req) => {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const url = new URL(req.url)
|
|
const url = new URL(req.url)
|
|
|
- const action = url.searchParams.get('action') || 'init'
|
|
|
|
|
|
|
+ const action = url.searchParams.get('action')
|
|
|
|
|
|
|
|
// Get environment variables
|
|
// Get environment variables
|
|
|
const supabaseUrl = Deno.env.get('SUPABASE_URL')!
|
|
const supabaseUrl = Deno.env.get('SUPABASE_URL')!
|
|
@@ -133,6 +133,130 @@ serve(async (req) => {
|
|
|
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
|
const frontendUrl = Deno.env.get('FRONTEND_URL') || 'https://shopcall.ai'
|
|
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
|
|
// Handle OAuth initiation
|
|
|
if (action === 'init') {
|
|
if (action === 'init') {
|
|
|
const storeUrl = url.searchParams.get('store_url')
|
|
const storeUrl = url.searchParams.get('store_url')
|
|
@@ -199,8 +323,9 @@ serve(async (req) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Build WooCommerce OAuth authorization URL
|
|
// 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 appName = 'ShopCall.ai'
|
|
|
const returnUrl = `${frontendUrl}/webshops?wc_connected=true`
|
|
const returnUrl = `${frontendUrl}/webshops?wc_connected=true`
|
|
|
|
|
|
|
@@ -212,6 +337,7 @@ serve(async (req) => {
|
|
|
authUrl.searchParams.set('callback_url', callbackUrl)
|
|
authUrl.searchParams.set('callback_url', callbackUrl)
|
|
|
|
|
|
|
|
console.log(`[WooCommerce] OAuth initiated for ${validation.normalized}`)
|
|
console.log(`[WooCommerce] OAuth initiated for ${validation.normalized}`)
|
|
|
|
|
+ console.log(`[WooCommerce] Callback URL: ${callbackUrl}`)
|
|
|
|
|
|
|
|
return new Response(
|
|
return new Response(
|
|
|
JSON.stringify({
|
|
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
|
|
// Unknown action
|
|
|
return new Response(
|
|
return new Response(
|
|
|
JSON.stringify({ error: 'Invalid action parameter' }),
|
|
JSON.stringify({ error: 'Invalid action parameter' }),
|