如何在生产商店上激活 Shopify Web Pixel 扩展?

问题描述 投票:0回答:1

问题:

您好,我正在尝试在生产商店上激活 Shopify 网络像素扩展。 Web 像素扩展在 Shopify Remix 应用模板中配置。后端连接到 Express.js。

我尝试过的:

方法_1: 我可以按照本指南使用 Shopify 应用程序开发 GraphQL 在开发/测试商店中激活它https://shopify.dev/docs/apps/build/marketing-analytics/build-web-pixels#:~:文本=To%20activate%20a%20web%20pixel,扩展名。 但是,我无法将其应用到生产商店,因为我无法在生产商店上运行

shopify app dev
来打开 shopify GraphQL 控制台。

mutation {
  # This mutation creates a web pixel, and sets the `accountID` declared in `shopify.extension.toml` to the value `123`.
  webPixelCreate(webPixel: { settings: "{\"accountID\":\"123\"}" }) {
    userErrors {
      code
      field
      message
    }
    webPixel {
      settings
      id
    }
  }
}

方法_2: 我还尝试过本指南,该指南使用 shopify remix 应用程序的加载程序功能激活像素。不幸的是它并没有那么好用。 https://community.shopify.com/c/extensions/how-do-you-actually-activate-the-web-pixel/m-p/2496617

shopify remix 应用模板 > 应用 app._index.jsx

export const loader = async ({ request }) => { // Authenticate with Shopify const { admin } = await authenticate.admin(request); 
const mutationResponse = await admin.graphql( #graphql
        mutation webPixelCreate($webPixel: WebPixelInput!) {
          webPixelCreate(webPixel: $webPixel) {
            userErrors {
              field
              message
            }
            webPixel {
              settings
              id
            }
          }
        }
      , { variables: { webPixel: { settings: { "accountID": 'Account_ID_45466_this_is_as_per_toml'
}, }, }, } ); 
if (!mutationResponse.ok) { console.error('Request failed', mutationResponse); return; } 
const data = await mutationResponse.json(); console.log(data); 
return mutationResponse; 
}; 

const loaderDataForWebPixel = useLoaderData();

Approach_3: Express.js 端点路由上的 Oauth 重定向和令牌交换>shopifyRouter.js 文件

import { shopify } from '../shopifyApi.js';
import { Router } from 'express';
import dotenv from 'dotenv';
import { shopify, RequestedTokenType } from '../shopifyApi.js'; 
import cookieParser from 'cookie-parser';
import axios from 'axios';

dotenv.config();


const router = Router();

// Middleware to set CSP headers for embedded apps
const setCSPHeaders = (req, res, next) => {
  const shop = req.query.shop;
  const shopDomain = shop ? `https://${shop}` : null;
  const shopifyAdminDomain = "https://admin.shopify.com";

  if (shopDomain) {
    res.setHeader("Content-Security-Policy", `frame-ancestors ${shopDomain} ${shopifyAdminDomain}`);
  } else {
    res.setHeader("Content-Security-Policy", `frame-ancestors ${shopifyAdminDomain}`);
  }
  next();
};


router.use((req, res, next) => {
  console.log(`Incoming request: ${req.method} ${req.url}`);
  next();
});


// Apply middleware
router.use(cookieParser());
router.use(setCSPHeaders);

// Route to handle the initial OAuth
router.get('/install', async (req, res) => {
  try {
    const shop = req.query.shop;

    if (!shop) {
      return res.status(400).json({ error: 'Missing "shop" query parameter' });
    }

    await shopify.auth.begin({
      shop,
      callbackPath: '/auth/callback',
      isOnline: false,
      rawRequest: req,
      rawResponse: res,
    });
  } catch (error) {
    console.error('Error during install:', error.message);
    console.error('Stack trace:', error.stack);
    res.status(500).json({ error: 'Error during install', message: error.message });
  }
});

// Route to handle the OAuth callback and activate web pixel
router.get('/auth/callback', async (req, res) => {
  try {
    const callbackResponse = await shopify.auth.callback({
      rawRequest: req,
      rawResponse: res,
    });
    const { session } = callbackResponse;
    const accessToken = session.accessToken;

    // Activate web pixel
    const graphqlUrl = `https://${session.shop}/admin/api/2023-07/graphql.json`;
    const graphqlHeaders = {
      'Content-Type': 'application/json',
      'X-Shopify-Access-Token': accessToken,
    };
    const graphqlMutation = {
      query: `
        mutation {
          webPixelCreate(webPixel: { settings: "{\\"accountID\\":\\"88888888\\"}" }) {
            userErrors {
              code
              field
              message
            }
            webPixel {
              settings
              id
            }
          }
        }
      `,
    };

    const graphqlResponse = await axios.post(graphqlUrl, graphqlMutation, { headers: graphqlHeaders });

    if (graphqlResponse.data.errors) {
      console.error('GraphQL errors:', graphqlResponse.data.errors);
      return res.status(500).json({ error: graphqlResponse.data.errors });
    }

    console.log('Web pixel activated:', graphqlResponse.data.data);
    res.json(graphqlResponse.data.data);
  } catch (error) {
    console.error('Error during OAuth callback:', error.message);
    console.error('Stack trace:', error.stack);
    res.status(500).json({ error: 'Error during OAuth callback', message: error.message });
  }
});

// Route get access token and activate web pixel
router.get('/auth', async (req, res) => {
  try {
    const shop = shopify.utils.sanitizeShop(req.query.shop, true);
    const headerSessionToken = getSessionTokenHeader(req);
    const searchParamSessionToken = getSessionTokenFromUrlParam(req);
    const sessionToken = headerSessionToken || searchParamSessionToken;

    

    if (!sessionToken) {
      return res.status(400).json({ error: 'Missing session token' });
    }

    const session = await shopify.auth.tokenExchange({
      sessionToken,
      shop,
      requestedTokenType: RequestedTokenType.OfflineAccessToken, // or RequestedTokenType.OnlineAccessToken
    });



    // Activate web pixel
    const accessToken = session.accessToken;
    console.log("🚀 ~ file: shopifyRouter.js:132 ~ router.get ~ accessToken:", accessToken);
    

    const graphqlUrl = `https://${shop}/admin/api/2023-07/graphql.json`;
    const graphqlHeaders = {
      'Content-Type': 'application/json',
      'X-Shopify-Access-Token': accessToken,
    };
    const graphqlMutation = {
      query: `
        mutation {
          webPixelCreate(webPixel: { settings: "{\\"accountID\\":\\"88888888\\"}" }) {
            userErrors {
              code
              field
              message
            }
            webPixel {
              settings
              id
            }
          }
        }
      `,
    };

    const graphqlResponse = await axios.post(graphqlUrl, graphqlMutation, { headers: graphqlHeaders });

    if (graphqlResponse.data.errors) {
      console.error('GraphQL errors:', graphqlResponse.data.errors);
      return res.status(500).json({ error: graphqlResponse.data.errors });
    }

    console.log('Web pixel activated:', graphqlResponse.data.data);
    res.json(graphqlResponse.data.data);
  } catch (error) {
    console.error('Error during token exchange:', error.message);
    console.error('Stack trace:', error.stack);
    res.status(500).json({ error: 'Error during token exchange', message: error.message });
  }
});

// Helper functions to get session token
function getSessionTokenHeader(request) {
  const authHeader = request.headers.authorization;
  if (authHeader && authHeader.startsWith('Bearer ')) {
    return authHeader.substring(7);
  }
  return null;
}

function getSessionTokenFromUrlParam(request) {
  return request.query.id_token || null;
}

export default router;

shopifyApi.js 文件

import '@shopify/shopify-api/adapters/node'; 
import { shopifyApi, LATEST_API_VERSION, RequestedTokenType } from '@shopify/shopify-api';


import dotenv from 'dotenv';

dotenv.config();

const myAppsLogFunction = (severity, message) => {
  console.log(`[${severity}] ${message}`);
};

let shopify;

try {
  shopify = shopifyApi({
    apiKey: process.env.SHOPIFY_API_KEY,
    apiSecretKey: process.env.SHOPIFY_API_SECRET,
    scopes: ['read_products', 'write_products', 'read_customer_events', 'write_pixels'],
    hostName: process.env.SHOPIFY_APP_HOST,    
    hostScheme: 'https',
    apiVersion: LATEST_API_VERSION,    
    isEmbeddedApp: true,
    isCustomStoreApp: false,
    userAgentPrefix: 'Custom prefix',
    logger: {
      log: (severity, message) => {
        myAppsLogFunction(severity, message);
      },
      level: 'info',
      httpRequests: true,
      timestamps: true,
    },
    future: {
      unstable_newEmbeddedAuthStrategy: true,
    }
  });

  
  
} catch (error) {
  console.log('shopifyApi.js error', error);
}

export { shopify, RequestedTokenType };

这是approach_3的结果

  • express-url-endpoint/install
    结果:
{
"error": 
"Error during Oauth callback", 
"message": Cannot complete Oauth process. 
Could not find an Oauth cookie for shop url: the-shop.myshopify.com"
}
  • express-url-endpoint/auth
    代币兑换结果为:
{
    "error:":
"Error during token exchange",
"message": "Request failed with status code 401"
}
express shopify shopify-app shopify-api shopify-api-node
1个回答
0
投票

您可以对端点进行 api 调用,然后执行 webPixelCreate 函数。在我的例子中,我使用了 fastapi 示例,但您也可以使用express.js 来做到这一点。

例如在

shopify.server.js
文件中

import "@shopify/shopify-app-remix/adapters/node";
import {
  AppDistribution,
  DeliveryMethod,
  shopifyApp,
  LATEST_API_VERSION,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import { restResources } from "@shopify/shopify-api/rest/admin/2024-01";
import prisma from "./db.server";
import createApp from "@shopify/app-bridge";
import { getSession } from "./sessions.server";

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
  apiVersion: LATEST_API_VERSION,
  scopes: process.env.SCOPES?.split(","),
  appUrl: process.env.SHOPIFY_APP_URL || "",
  authPathPrefix: "/auth",
  sessionStorage: new PrismaSessionStorage(prisma),
  distribution: AppDistribution.AppStore,
  restResources,
  webhooks: {
    APP_UNINSTALLED: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: `${process.env.APP_HOST}/webhooks`,
    },

  },
  hooks: {
    afterAuth: async ({ session }) => {
      shopify.registerWebhooks({ session });

      try {
        const response = await fetch(
          process.env.OCTY_APP_HOST + "https://myserver.com/app_installed",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              shop: session.shop,
              access_token: session.accessToken,
              api_version: apiVersion,
            }),
          }
        );
        const data = await response.json();
        console.log(data);
      } catch (error) {
        console.error(error);
      }
    },
  },

  ...(process.env.SHOP_CUSTOM_DOMAIN
    ? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
    : {}),
});

export default shopify;
export const apiVersion = LATEST_API_VERSION;
export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
export const authenticate = shopify.authenticate;
export const unauthenticated = shopify.unauthenticated;
export const login = shopify.login;
export const registerWebhooks = shopify.registerWebhooks;
export const sessionStorage = shopify.sessionStorage;

这里我正在调用我的 fastapi 端点

/app_installed

@app.post("/app_installed")
async def process_app_install(request: Request):
    try:
        
        # variables for demo purposes
        store = request.query_params.get('store')
        access_token = request.query_params.get('access_token')
        api_version = request.query_params.get('api_version')

        for retry_count in range(max_retries):
            mutation = """
                mutation($settings: JSON!) {
                  webPixelCreate(webPixel: { settings: $settings }) {
                    userErrors {
                      code
                      field
                      message
                    }
                    webPixel {
                      settings
                      id
                    }
                  }
                }
                """

            settings = json.dumps({"accountID": store})

            variables = {
                "settings": settings
            }

            api_endpoint = f'https://{store}/admin/api/{api_version}/graphql.json'

            headers = {
                'X-Shopify-Access-Token': access_token,
                'Content-Type': 'application/json',
            }

            response = requests.post(api_endpoint, json={'query': mutation, 'variables': variables},
                                     headers=headers)

            # process response as fit

    except HTTPException as e:
        # return detailed error body
        return {"status": "error",
                "message": "Error occurred while processing the request."}
© www.soinside.com 2019 - 2024. All rights reserved.