diff --git a/.env.example b/.env.example index f7fa990d41..da1e7f4044 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,4 @@ SITE_NAME="Next.js Commerce" SHOPIFY_REVALIDATION_SECRET="" SHOPIFY_STOREFRONT_ACCESS_TOKEN="" SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com" +SHOPIFY_STOREFRONT_API_VERSION= "2025-07" diff --git a/lib/constants.ts b/lib/constants.ts index 56bc6cd12d..f3a20d1f8f 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -28,4 +28,4 @@ export const TAGS = { export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden'; export const DEFAULT_OPTION = 'Default Title'; -export const SHOPIFY_GRAPHQL_API_ENDPOINT = '/api/2023-01/graphql.json'; +export const SHOPIFY_GRAPHQL_API_ENDPOINT = `/api/${process.env.SHOPIFY_STOREFRONT_API_VERSION}/graphql.json`; diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index b90893172b..f35f619b38 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -61,8 +61,11 @@ import { const domain = process.env.SHOPIFY_STORE_DOMAIN ? ensureStartsWith(process.env.SHOPIFY_STORE_DOMAIN, 'https://') : ''; -const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`; -const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!; + +export const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`; + +// Your Shopify Storefront access token +export const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!; type ExtractVariables = T extends { variables: object } ? T['variables'] @@ -76,7 +79,7 @@ export async function shopifyFetch({ headers?: HeadersInit; query: string; variables?: ExtractVariables; -}): Promise<{ status: number; body: T } | never> { +}): Promise<{ status: number; body: T }> { try { const result = await fetch(endpoint, { method: 'POST', @@ -93,28 +96,35 @@ export async function shopifyFetch({ const body = await result.json(); + // Handle API-level errors if (body.errors) { - throw body.errors[0]; + console.error('[shopifyFetch] Shopify API error:', { + query, + status: result.status, + error: body.errors[0] + }); + + // Throw a standard Error object (not a plain object) + throw new Error( + `Shopify API Error (${result.status}): ${body.errors[0].message}` + ); } return { status: result.status, body }; - } catch (e) { - if (isShopifyError(e)) { - throw { - cause: e.cause?.toString() || 'unknown', - status: e.status || 500, - message: e.message, - query - }; - } + } catch (err: any) { + // Log full context for unknown or fetch-level errors + console.error('[shopifyFetch] Unexpected fetch error:', { + query, + error: err + }); - throw { - error: e, - query - }; + // Re-throw a standard Error to be caught elsewhere + throw new Error( + `Unexpected error in shopifyFetch: ${err?.message ?? err}` + ); } } @@ -264,23 +274,29 @@ export async function updateCart( } export async function getCart(): Promise { - const cartId = (await cookies()).get('cartId')?.value; + try { + const cartId = (await cookies()).get('cartId')?.value; - if (!cartId) { - return undefined; - } + if (!cartId) { + console.warn('[getCart] No cartId in cookies'); + return undefined; + } - const res = await shopifyFetch({ - query: getCartQuery, - variables: { cartId } - }); + const res = await shopifyFetch({ + query: getCartQuery, + variables: { cartId } + }); - // Old carts becomes `null` when you checkout. - if (!res.body.data.cart) { + if (!res.body?.data?.cart) { + console.warn('[getCart] Cart is null for cartId:', cartId); + return undefined; + } + + return reshapeCart(res.body.data.cart); + } catch (err) { + console.error('[getCart] Failed:', err); return undefined; } - - return reshapeCart(res.body.data.cart); } export async function getCollection(