问题:
您好,我正在尝试在生产商店上激活 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"
}
您可以对端点进行 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."}