Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions apps/web/app/(ee)/api/shopify/integration/webhook/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { captureWebhookLog } from "@/lib/api-logs/capture-webhook-log";
import { isLocalDev } from "@/lib/api/environment";
import { prisma } from "@/lib/prisma";
import { log } from "@dub/utils";
import { waitUntil } from "@vercel/functions";
import { logAndRespond } from "app/(ee)/api/cron/utils";
import crypto from "crypto";
import { appUninstalled } from "./app-uninstalled";
import { customersDataRequest } from "./customers-data-request";
Expand All @@ -27,6 +27,12 @@ export const POST = async (req: Request) => {
const headers = req.headers;
const topic = headers.get("x-shopify-topic") || "";
const signature = headers.get("x-shopify-hmac-sha256") || "";
const shopDomain = headers.get("x-shopify-shop-domain") || "";

console.info("Webhook event", {
shopDomain,
topic,
});

if (!isLocalDev) {
// Verify signature
Expand All @@ -36,19 +42,21 @@ export const POST = async (req: Request) => {
.digest("base64");

if (generatedSignature !== signature) {
return new Response(`[Shopify] Invalid webhook signature. Skipping...`, {
status: 401,
});
return logAndRespond(
"Shopify webhook signature verification failed. Skipping...",
{
status: 401,
},
);
}
}

// Check if topic is relevant
if (!relevantTopics.has(topic)) {
return new Response(`[Shopify] Unsupported topic: ${topic}. Skipping...`);
return logAndRespond(`Unsupported topic: ${topic}. Skipping...`);
}

const event = JSON.parse(data);
const shopDomain = headers.get("x-shopify-shop-domain") || "";

// Find workspace
const workspace = await prisma.project.findUnique({
Expand All @@ -63,11 +71,15 @@ export const POST = async (req: Request) => {
});

if (!workspace) {
return new Response(
`[Shopify] Workspace not found for shop: ${shopDomain}. Skipping...`,
return logAndRespond(
`Workspace not found for shop: ${shopDomain}. Skipping...`,
);
}

console.info("Workspace found", {
workspaceId: workspace.id,
});

const requestLog = {
workspaceId: workspace.id,
method: req.method,
Expand Down Expand Up @@ -111,14 +123,7 @@ export const POST = async (req: Request) => {
break;
}
} catch (error) {
await log({
message: `Shopify webhook failed. Error: ${error.message}`,
type: "errors",
});

const response = new Response(
`[Shopify] Webhook handler failed. View logs`,
);
const response = new Response("Webhook handler failed. View logs.");

waitUntil(
captureWebhookLog({
Expand Down
6 changes: 6 additions & 0 deletions apps/web/app/(ee)/api/shopify/pixel/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export const POST = async (req: Request) => {
});
}

console.info("Shopify pixel event", {
clickId,
checkoutToken,
});

// Rate limit the request
const ip = process.env.VERCEL === "1" ? ipAddress(req) : LOCALHOST_IP;
const { success } = await ratelimit().limit(`shopify-track-pixel:${ip}`);
Expand All @@ -39,6 +44,7 @@ export const POST = async (req: Request) => {
const clickEvent = await getClickEvent({ clickId });

if (!clickEvent) {
console.warn(`Click event not found for the clickId ${clickId}`);
clickId = undefined;
}
}
Expand Down
24 changes: 24 additions & 0 deletions apps/web/lib/integrations/shopify/checkout-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,28 @@ export async function tryDispatchShopifyOrderJob({
checkoutToken: string;
checkout: ShopifyCheckoutCacheItem;
}) {
const logContext = {
checkoutToken,
workspaceId: checkout.workspaceId,
clickId: checkout.clickId,
hasClickId: Boolean(checkout.clickId),
hasOrder: Boolean(checkout.order),
dispatched: Boolean(checkout.dispatched),
};

if (!checkout.order || !checkout.workspaceId || !checkout.clickId) {
console.info(
"Shopify order dispatch skipped: checkout incomplete",
logContext,
);
return false;
}

if (checkout.dispatched) {
console.info(
"Shopify order dispatch skipped: already dispatched",
logContext,
);
return false;
}

Expand All @@ -79,6 +96,7 @@ export async function tryDispatchShopifyOrderJob({
const claimed = Boolean(claim);

if (!claimed) {
console.info("Shopify order dispatch skipped: claim lost", logContext);
return false;
}

Expand All @@ -94,11 +112,17 @@ export async function tryDispatchShopifyOrderJob({
},
);
} catch (error) {
console.error("Shopify order dispatch failed, releasing claim", {
...logContext,
error,
});
await redis.hdel(key, "dispatched");
throw error;
}

await shopifyCheckoutCache.delete(checkoutToken);

console.info("Shopify order job dispatched", logContext);

return true;
}
Loading