/** * Shopify Storefront API Client * Custom implementation using Cart API (2026-01) */ class ShopifyCart { constructor(config) { this.domain = config.domain; this.storefrontAccessToken = config.storefrontAccessToken; this.apiVersion = '2026-01'; this.endpoint = `https://${this.domain}/api/${this.apiVersion}/graphql.json`; this.cartId = null; this.cartItems = []; // Load existing cart from localStorage this.loadCart(); } /** * Make GraphQL request to Shopify Storefront API */ async query(query, variables = {}) { const response = await fetch(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Storefront-Access-Token': this.storefrontAccessToken, }, body: JSON.stringify({ query, variables }), }); const result = await response.json(); if (result.errors) { console.error('Shopify API Error:', result.errors); throw new Error(result.errors[0].message); } return result.data; } /** * Get product information by ID */ async getProduct(productId) { const query = ` query getProduct($id: ID!) { product(id: $id) { id title description availableForSale variants(first: 10) { edges { node { id title price { amount currencyCode } availableForSale } } } } } `; const data = await this.query(query, { id: `gid://shopify/Product/${productId}` }); return data.product; } /** * Get product by handle * @param {string} handle - Product handle (slug) */ async getProductByHandle(handle) { const query = ` query getProductByHandle($handle: String!) { product(handle: $handle) { id handle title description descriptionHtml availableForSale tags titleEn: metafield(namespace: "custom", key: "title_en") { value } descriptionEn: metafield(namespace: "custom", key: "description_en") { value } priceRange { minVariantPrice { amount currencyCode } } images(first: 10) { edges { node { id url altText width height } } } variants(first: 20) { edges { node { id title sku availableForSale price { amount currencyCode } selectedOptions { name value } } } } } } `; const data = await this.query(query, { handle }); return data.product || null; } /** * Get all products for listing page * @param {number} first - Number of products to fetch */ async getAllProducts(first = 20) { const query = ` query getAllProducts($first: Int!) { products(first: $first, sortKey: TITLE) { edges { node { id handle title description availableForSale titleEn: metafield(namespace: "custom", key: "title_en") { value } priceRange { minVariantPrice { amount currencyCode } } images(first: 1) { edges { node { id url altText } } } } } pageInfo { hasNextPage endCursor } } } `; const data = await this.query(query, { first }); return data.products; } /** * Create a new cart */ async createCart(lines = []) { const query = ` mutation cartCreate($input: CartInput!) { cartCreate(input: $input) { cart { id checkoutUrl lines(first: 10) { edges { node { id quantity merchandise { ... on ProductVariant { id title price { amount currencyCode } product { title } } } } } } } userErrors { field message } } } `; const data = await this.query(query, { input: { lines } }); if (data.cartCreate.userErrors.length > 0) { throw new Error(data.cartCreate.userErrors[0].message); } this.cartId = data.cartCreate.cart.id; this.saveCart(); return data.cartCreate.cart; } /** * Add item to cart */ async addToCart(variantId, quantity = 1) { const lines = [{ merchandiseId: `gid://shopify/ProductVariant/${variantId}`, quantity: quantity }]; let cart; if (this.cartId) { // Add to existing cart const query = ` mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) { cartLinesAdd(cartId: $cartId, lines: $lines) { cart { id checkoutUrl lines(first: 10) { edges { node { id quantity merchandise { ... on ProductVariant { id title price { amount currencyCode } product { title } } } } } } } userErrors { field message } } } `; const data = await this.query(query, { cartId: this.cartId, lines }); if (data.cartLinesAdd.userErrors.length > 0) { throw new Error(data.cartLinesAdd.userErrors[0].message); } cart = data.cartLinesAdd.cart; } else { // Create new cart cart = await this.createCart(lines); } this.cartItems = cart.lines.edges; return cart; } /** * Get checkout URL to redirect user */ getCheckoutUrl(cart) { return cart?.checkoutUrl || null; } /** * Save cart ID to localStorage */ saveCart() { if (this.cartId) { localStorage.setItem('shopify_cart_id', this.cartId); } } /** * Load cart ID from localStorage */ loadCart() { this.cartId = localStorage.getItem('shopify_cart_id'); } /** * Clear cart */ clearCart() { this.cartId = null; this.cartItems = []; localStorage.removeItem('shopify_cart_id'); } } // Export for use in other scripts window.ShopifyCart = ShopifyCart;