// EXTREMELY inspired by https://github.com/webdjoe/shopify_apps_script/blob/master/shopify_api.gs // Create a private App in Shopify admin and insert app password below // Sheets need to be created before running with columns of desired output set // Use keys found in Shopify Rest API Order Object as row headers // https://shopify.dev/api/admin-rest/2022-04/resources/order#resource-object // Supports the following sheets/order objects with default columns - // orders - id, number, order_name, name, current_total_discounts, current_total_price, current_subtotal_price, current_total_tax, financial_status, landing_site, created_at, processed_at, processing_method, subtotal_price, source_url, tags, token, total_discounts, total_line_items_price, total_price, total_tax, updated_at // shipping_lines - order_id, ordered_at, code, price, title, source, discounted_price, carrier_identifier // discount_applications - order_id, created_at, type, title, value, value_type, code, description, target_type, target_selection, allocation_method, ordered_at // refunds = id, order_id, ordered_at, created_at, note, transaction_id, kind, amount // line_items = id, order_id, sku, name, price, title, vendor, quantity, product_id, variant_id, variant_title, total_discount, fulfillment_status, ordered_at // customer - order_id, ordered_at, id, email, phone, last_name, created_at, first_name, admin_graphql_api_id, updated_at, orders_count, last_order_id /// import { Config } from "./config" import * as shopify from "shopify-admin-api-typings" import gql from 'graphql-tag' import { IShop } from "./interfaces/IShop" const ss = SpreadsheetApp.getActive() const today = new Date() const thirty_days = new Date( today.getFullYear(), today.getMonth() - 1, today.getDate() ) const start_str = Utilities.formatDate( thirty_days, SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone(), "yyyy-MM-dd" ) const end_str = Utilities.formatDate( today, SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone(), "yyyy-MM-dd" ) const orders = "_orders" const line_items = "_line_items" const customer = "_customer" const refunds = "_refunds" const shipping_lines = "_shipping_lines" const discount_applications = "_discount_applications" function makeArray(headers, obj_list) { var rng_arr = [] for (let obj of obj_list) { var line_arr = [] headers.forEach((item) => { var line_item = item in obj ? obj[item] : "" if (Array.isArray(line_item)) { line_item = line_item.toString() } line_arr.push(line_item) }) rng_arr.push(line_arr) } return rng_arr } function makeDiscountsArray(orders_arr) { var disc_arr = [] var headers = getDiscountHeaders() for (let ord of orders_arr) { var disc_list = ord.discount_applications if (disc_list.length < 1) { continue } disc_list.forEach((disc) => { var disc_line = [] disc["order_id"] = ord["id"] disc["ordered_at"] = ord["created_at"] headers.forEach((head) => { var itm = head in disc ? disc[head] : "" disc_line.push(itm) }) disc_arr.push(disc_line) }) } return disc_arr } function makeCustomerArray(orders_arr) { var cust_arr = [] var headers = getCustomerHeaders() for (let ord of orders_arr) { var cust_obj = ord.customer if (typeof cust_obj == "undefined") { continue } var customer_line = [] cust_obj["order_id"] = ord["id"] cust_obj["ordered_at"] = ord["created_at"] headers.forEach((head) => { var itm = head in cust_obj ? cust_obj[head] : "" customer_line.push(itm) }) cust_arr.push(customer_line) } return cust_arr } function makeLIArray(orders_arr) { var line_arr = [] var headers = getLineItemHeaders() for (let ord of orders_arr) { var line_items_list = ord.line_items if (line_items_list.length < 1) { continue } line_items_list.forEach((li_obj) => { var li_line = [] li_obj["ordered_at"] = ord["created_at"] li_obj["order_id"] = ord["id"] headers.forEach((head) => { var itm = head in li_obj ? li_obj[head] : "" li_line.push(itm) }) line_arr.push(li_line) }) } return line_arr } function makeShippingArray(orders_arr) { var ship_arr = [] var header = getShippingHeaders() for (let ord of orders_arr) { var ship_list = ord.shipping_lines if (ship_list.length < 1) { continue } ship_list.forEach((ship_obj) => { var line = [] ship_obj["ordered_at"] = ord["created_at"] ship_obj["order_id"] = ord["id"] header.forEach((a) => { var line_itm = a in ship_obj ? ship_obj[a] : "" line.push(line_itm) }) ship_arr.push(line) }) } return ship_arr } function makeRefundsArray(orders_arr) { var refunds_arr = [] var header = getRefundHeaders() for (let ord of orders_arr) { var ref_list = ord.refunds if (ref_list.length < 1) { continue } ref_list.forEach((ref_obj) => { var line = [] ref_obj["ordered_at"] = ord["created_at"] if (ref_obj["transactions"].length == 1) { var trans = ref_obj["transactions"][0] ref_obj["transaction_id"] = trans["id"] ref_obj["amount"] = trans["amount"] ref_obj["kind"] = trans["kind"] } else { return } header.forEach((a) => { var line_itm = a in ref_obj ? ref_obj[a] : "" line.push(line_itm) }) refunds_arr.push(line) }) } return refunds_arr } function appendRows(sht_name, arr) { var sht = ss.getSheetByName(sht_name) let last_row = sht.getLastRow() sht.getRange(last_row + 1, 1, arr.length, arr[0].length).setValues(arr) } function processDataRange(sht_name) { var sh = ss.getSheetByName(sht_name) var data_rng = sh.getDataRange().offset(1, 0, sh.getLastRow() - 1) var data = data_rng.getValues() var targetData = new Array() for (let n = 0; n < data.length; ++n) { if (data[n].join().replace(/,/g, "") != "") { targetData.push(data[n]) } } data_rng.clear() targetData.sort((a, b) => a[1] - b[1]) sh.getRange(2, 1, targetData.length, targetData[0].length).setValues( targetData ) } function removeDuplicates(obj_list, sht_name, id = "id") { var sht = ss.getSheetByName(sht_name) var sht_arr = sht.getDataRange().getValues() for (let a = 0; a < sht_arr[0].length; a++) { if (sht_arr[0][a] == id) { var id_col = a break } } for (let x = 0; x < sht_arr.length; x++) { var row_id = sht_arr[x][id_col] for (let y = 0; y < obj_list.length; y++) { if (obj_list[y][id_col] == row_id) { sht.getRange(x + 1, 1, 1, sht_arr[0].length).clearContent() break } } } } function removeAllDuplicates(obj_list, sht_name, id = "id") { var sht = ss.getSheetByName(sht_name) var sht_arr = sht.getDataRange().getValues() for (let a = 0; a < sht_arr[0].length; a++) { if (sht_arr[0][a] == id) { var id_col = a break } } for (let x = 0; x < sht_arr.length; x++) { var row_id = sht_arr[x][id_col] for (let y = 0; y < obj_list.length; y++) { if (obj_list[y][id_col] == row_id) { sht.getRange(x + 1, 1, 1, sht_arr[0].length).clearContent() } } } } function getColumnList() { var order_list = getOrderHeaders() order_list.push( line_items, customer, refunds, discount_applications, shipping_lines ) return order_list } function getColumns(data: string) { var sht = ss.getSheetByName(data) if (sht == null) { ss.insertSheet(data) sht = ss.getSheetByName(data) } if (sht == null) { throw new Error("unable to get/create sheet " + data) } var last_col = sht.getLastColumn() var header_list = sht.getRange(1, 1, 1, last_col).getValues() return header_list[0] } function getShippingHeaders() { return getColumns(shipping_lines) } function getCustomerHeaders() { return getColumns(customer) } function getLineItemHeaders() { return getColumns(line_items) } function getOrderHeaders() { return Order.columns } function getDiscountHeaders() { return getColumns(discount_applications) } function getRefundHeaders() { return getColumns(refunds) } function customerTable(orders_arr) { var rng_arr = makeCustomerArray(orders_arr) removeDuplicates(rng_arr, customer, "order_id") SpreadsheetApp.flush() appendRows(customer, rng_arr) SpreadsheetApp.flush() processDataRange(customer) } function discountsTable(orders_arr) { var rng_arr = makeDiscountsArray(orders_arr) if (rng_arr.length > 0) { removeAllDuplicates(rng_arr, discount_applications, "order_id") SpreadsheetApp.flush() appendRows(discount_applications, rng_arr) SpreadsheetApp.flush() processDataRange(discount_applications) } } function ordersTable(orders_arr) { var rng_arr = makeArray(getOrderHeaders(), orders_arr) removeDuplicates(rng_arr, orders, "id") SpreadsheetApp.flush() appendRows(orders, rng_arr) SpreadsheetApp.flush() processDataRange(orders) } function refundsTable(orders_arr) { var rng_arr = makeRefundsArray(orders_arr) if (rng_arr.length > 0) { removeDuplicates(rng_arr, refunds, "id") SpreadsheetApp.flush() appendRows(refunds, rng_arr) SpreadsheetApp.flush() processDataRange(refunds) } } function lineItemsTable(orders_arr) { var rng_arr = makeLIArray(orders_arr) removeDuplicates(rng_arr, line_items, "id") SpreadsheetApp.flush() appendRows(line_items, rng_arr) SpreadsheetApp.flush() processDataRange(line_items) } function shippingTable(orders_arr) { var rng_arr = makeShippingArray(orders_arr) if (rng_arr.length > 0) { removeDuplicates(rng_arr, shipping_lines, "order_id") SpreadsheetApp.flush() appendRows(shipping_lines, rng_arr) SpreadsheetApp.flush() processDataRange(shipping_lines) } } function unquote(value) { if (value.charAt(0) == '"' && value.charAt(value.length - 1) == '"') return value.substring(1, value.length - 1) return value } function parseLinkHeader(header) { var linkexp = /<[^>]*>\s*(\s*;\s*[^\(\)<>@,;:"\/\[\]\?={} \t]+=(([^\(\)<>@,;:"\/\[\]\?={} \t]+)|("[^"]*")))*(,|$)/g var paramexp = /[^\(\)<>@,;:"\/\[\]\?={} \t]+=(([^\(\)<>@,;:"\/\[\]\?={} \t]+)|("[^"]*"))/g var matches = header.match(linkexp) var rels = {} for (let i = 0; i < matches.length; i++) { var split = matches[i].split(">") var href = split[0].substring(1) var ps = split[1] var link = { href: "", rel: "", } link.href = href var s = ps.match(paramexp) for (let j = 0; j < s.length; j++) { var p = s[j] var paramsplit = p.split("=") var name = paramsplit[0] link[name] = unquote(paramsplit[1]) } if (link.rel !== undefined) { rels[link.rel] = link } } return rels } export class Shop implements IShop { private shopifyApiKey: string private shopifyApiSecretKey: string private shopifyAdminApiAccessToken: string private shopifyApiURI: string static endpoints = { orders: "/admin/api/2022-04/orders.json", graphql: "/admin/api/2024-10/graphql.json", } constructor() { let config = new Config() this.shopifyApiKey = config.shopifyApiKey this.shopifyApiSecretKey = config.shopifyApiSecretKey this.shopifyAdminApiAccessToken = config.shopifyAdminApiAccessToken this.shopifyApiURI = config.shopifyApiURI } RunOrders(start = "", end = "") { if (start === "" || end === "") { start = start_str end = end_str } const date_exp = /^\d{4}[\-](0?[1-9]|1[012])[\-](0?[1-9]|[12][0-9]|3[01])$/ if (!date_exp.test(start) || !date_exp.test(end)) { Logger.log("Dates must be in yyyy-MM-dd format") return } this.GetOrders(start, end) } GetOrders(start: string, end: string) { let endpoint = Shop.endpoints.orders var params = { created_at_min: start, created_at_max: end, fields: getColumnList(), } var done = false var next_link = "" do { if (next_link === "") { var response = this.shopifyAPI(endpoint, params) console.log(response) } else { var response = this.shopifyAPI(endpoint, params, next_link) console.log(response) } let resp = response.content let headers = response.headers var orders_arr: Order[] = resp["orders"] if (orders_arr.length < 1) { console.log("No orders found") done = true continue } var cols = getColumnList() var numColumns = cols.length var respKeys = Object.keys(orders_arr[0]) var respCols = respKeys.length if (cols.filter((x) => respKeys.indexOf(x) === -1).length !== 0) { Logger.log( "Keys missing from return - " + cols.filter((x) => respKeys.indexOf(x) === -1) ) return } ordersTable(orders_arr) refundsTable(orders_arr) lineItemsTable(orders_arr) customerTable(orders_arr) discountsTable(orders_arr) shippingTable(orders_arr) if (headers["Link"] !== undefined) { var links = parseLinkHeader(headers["Link"]) Logger.log(headers["Link"]) if (links["next"] === undefined) { done = true } else { next_link = links["next"]["href"] Logger.log(next_link) } } else { done = true } } while (!done) } FetchOrders(start: Date, end: Date) { let endpoint = Shop.endpoints.orders let start_str = start.toISOString() let end_str = end.toISOString() var params = { created_at_min: start_str, created_at_max: end_str, fields: "id,created_at,financial_status,name,line_items,subtotal_price,total_price,total_tax", status: "any" } var all_orders: any[] = [] var done = false var next_link = "" do { var response if (next_link === "") { response = this.shopifyAPI(endpoint, params) } else { response = this.shopifyAPI(endpoint, params, next_link) } let resp = response.content let headers = response.headers var orders_arr = resp["orders"] if (orders_arr.length > 0) { all_orders = all_orders.concat(orders_arr) } if (headers["Link"] !== undefined) { var links = parseLinkHeader(headers["Link"]) if (links["next"] === undefined) { done = true } else { next_link = links["next"]["href"] } } else { done = true } } while (!done) return all_orders } GetProducts() { let done = false let query = "" let cursor = "" let fields = ["id", "title"] var response = { content: {}, headers: {}, } let products: ShopifyProduct[] = [] do { let pq = new ShopifyProductsQuery(query, fields, cursor) response = this.shopifyGraphQLAPI(pq.JSON) console.log(response) let productsResponse = new ShopifyProductsResponse(response.content) if (productsResponse.products.edges.length <= 0) { console.log("no products returned") done = true continue } for (let i = 0; i < productsResponse.products.edges.length; i++) { let edge = productsResponse.products.edges[i] console.log(JSON.stringify(edge)) let p = new ShopifyProduct() Object.assign(edge.node, p) products.push(p) } if (productsResponse.products.pageInfo.hasNextPage) { cursor = productsResponse.products.pageInfo.endCursor } else { done = true } } while (!done) } GetProductBySku(sku: string) { console.log("GetProductBySku('" + sku + "')") let gql = /* GraphQL */ ` query productBySku { products(first: 1, query: "sku:${sku}") { edges { node { id title handle variants(first: 1) { nodes { id sku } } options { id name optionValues { id name } } } } } } ` let query = buildGqlQuery(gql, {}) let response = this.shopifyGraphQLAPI(query) if (response.content.data.products.edges.length <= 0) { console.log("GetProductBySku: no product matched") return } let product = response.content.data.products.edges[0].node console.log("Product found:\n" + JSON.stringify(product, null, 2)) return product } GetProductById(id: string) { console.log("GetProductById('" + id + "')") let gql = /* GraphQL */ ` query productById { product(id: "${id}") { id title handle variants(first: 1) { nodes { id sku } } } } ` let query = buildGqlQuery(gql, {}) let response = this.shopifyGraphQLAPI(query) if (!response.content.data.product) { console.log("GetProductById: no product matched") return null; } let product = response.content.data.product console.log("Product found:\n" + JSON.stringify(product, null, 2)) return product } GetInventoryItemBySku(sku: string) { console.log('GetInventoryItemBySku("' + sku + '")') let gql = /* GraphQL */ ` query inventoryItems { inventoryItems(first:1, query:"sku:${sku}") { edges { node { id tracked sku } } } } ` let query = buildGqlQuery(gql, {}) let response = this.shopifyGraphQLAPI(query) let item: shopify.InventoryItem = response.content.data.inventoryItems.edges[0].node console.log( "GetInventoryItemBySku: found item:\n" + JSON.stringify(item, null, 2) ) return item } UpdateInventoryItemQuantity( item: shopify.InventoryItem, delta: number = 1, config: Config ) { console.log("UpdateInventoryItemQuantity(" + JSON.stringify(item) + ")") let gql = /* GraphQL */ ` mutation inventoryAdjustQuantities( $input: InventoryAdjustQuantitiesInput! ) { inventoryAdjustQuantities(input: $input) { userErrors { field message } inventoryAdjustmentGroup { createdAt reason referenceDocumentUri changes { name delta } } } } ` let variables = { input: { reason: "correction", name: "available", changes: [ { delta: delta, inventoryItemId: item.id, locationId: config.shopifyLocationId, }, ], }, } let query = buildGqlQuery(gql, variables) let response = this.shopifyGraphQLAPI(query) let newItem: shopify.InventoryItem = response.content console.log("new item:\n" + JSON.stringify(newItem, null, 2)) return newItem } SetInventoryItemQuantity( item: shopify.InventoryItem, quantity: number, config: Config ) { console.log("SetInventoryItemQuantity(" + JSON.stringify(item) + ", " + quantity + ")") let gql = /* GraphQL */ ` mutation inventorySetQuantities($input: InventorySetQuantitiesInput!) { inventorySetQuantities(input: $input) { inventoryAdjustmentGroup { changes { name delta } } userErrors { field message } } } ` let variables = { input: { name: "available", reason: "correction", ignoreCompareQuantity: true, quantities: [ { inventoryItemId: item.id, locationId: config.shopifyLocationId, quantity: quantity, }, ], }, } let query = buildGqlQuery(gql, variables) let response = this.shopifyGraphQLAPI(query) // Response structure is different for setQuantities console.log("SetInventoryItemQuantity response:\n" + JSON.stringify(response, null, 2)) return response.content } SetInventoryItemDefaults(item: shopify.InventoryItem, config: Config) { let gql = /* GraphQL */ ` mutation inventoryItemUpdate($id: ID!, $input: InventoryItemInput!) { inventoryItemUpdate(id: $id, input: $input) { inventoryItem { id unitCost { amount } tracked countryCodeOfOrigin provinceCodeOfOrigin harmonizedSystemCode countryHarmonizedSystemCodes(first: 1) { edges { node { harmonizedSystemCode countryCode } } } } userErrors { message } } } ` let variables = { id: item.id, input: { tracked: true, countryCodeOfOrigin: config.shopifyCountryCodeOfOrigin, provinceCodeOfOrigin: config.shopifyProvinceCodeOfOrigin, }, } let query = buildGqlQuery(gql, variables) let response = this.shopifyGraphQLAPI(query) let newItem: shopify.InventoryItem = response.content return newItem } SetInventoryItemWeight(item: shopify.InventoryItem, config: Config, weight: number, weight_unit: shopify.WeightUnit) { let gql = /* GraphQL */ ` mutation inventoryItemUpdate($id: ID!, $input: InventoryItemInput!) { inventoryItemUpdate(id: $id, input: $input) { inventoryItem { id measurement { weight { value unit } } } userErrors { field message } } } ` let variables = { id: item.id, input: { measurement: { weight: { value: weight, unit: weight_unit } } }, } let query = buildGqlQuery(gql, variables) let response = this.shopifyGraphQLAPI(query) let newItem: shopify.InventoryItem = response.content return newItem } shopifyAPI(endpoint: string, query: {}, next = "") { var options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = { method: "get", headers: { "X-Shopify-Access-Token": this.shopifyAdminApiAccessToken, }, } if (next == "") { var url = this.buildURL(endpoint, query) var resp = UrlFetchApp.fetch(url, options) console.log(resp.getContentText()) } else { var url = next var resp = UrlFetchApp.fetch(url, options) console.log(resp.getContentText()) } return { content: JSON.parse(resp.getContentText()), headers: resp.getHeaders(), } } shopifyGraphQLAPI(query: {}, next = "") { console.log("shopifyGraphQLAPI:query: " + JSON.stringify(query)) let endpoint = Shop.endpoints.graphql let options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = { method: "post", payload: JSON.stringify(query), headers: { "Content-Type": "application/json", "X-Shopify-Access-Token": this.shopifyAdminApiAccessToken, }, muteHttpExceptions: true, } var url = this.buildURL(endpoint) console.log( "shopifyGraphQLAPI sending request:\n" + JSON.stringify(UrlFetchApp.getRequest(url, options), null, 2) ) var resp = UrlFetchApp.fetch(url, options) let content = resp.getContentText() console.log("shopifyGraphQLAPI got response:\n" + content) let content_json = JSON.parse(content) console.log(JSON.stringify(content_json, null, 2)) return { content: content_json, headers: resp.getHeaders(), } } buildURL(endpoint: string, params = {}) { var url = this.shopifyApiURI + endpoint if (params !== undefined && Object.keys(params).length > 0) { let i = 0 for (let p in params) { if (i === 0) { url += "?" + p + "=" + encodeURIComponent(params[p]) } else { url += "&" + p + "=" + encodeURIComponent(params[p]) } i++ } } return url } getShopDomain(): string { // Extract from https://{shop}.myshopify.com return this.shopifyApiURI.replace('https://', '').replace(/\/$/, ''); } } export class Order { static columns = [ "id", "number", "order_name", "name", "current_total_discounts", "current_total_price", "current_subtotal_price", "current_total_tax", "financial_status", "landing_site", "created_at", "processed_at", "processing_method", "subtotal_price", "source_url", "tags", "token", "total_discounts", "total_line_items_price", "total_price", "total_tax", "updated_at", ] constructor() { for (let i = 0; i < Order.columns.length; i++) { this[Order.columns[i]] = "" } } } export class ShopifyProduct { body_html: string created_at: Date handle: string id: string images: ProductImage[] options: ProductOption[] product_type: string published_at: Date published_scope: string status: string tags: string[] template_suffix: string title: string updated_at: Date variants: ShopifyVariantNodes vendor: string } export class ShopifyVariantNodes { nodes: ShopifyProductVariant[] } class ProductImage { id: number product_id: number position: number created_at: Date updated_at: Date width: number height: number src: string variant_ids: number[] } class ProductOption { id: string product_id: number name: string position: number values: string[] optionValues?: ShopifyProductOptionValues } export class ShopifyProductOptionValues { id?: string name?: string } export class ShopifyVariantOptionValueInput { id?: string linkedMetafieldValue?: string name?: string optionId?: string optionName?: string } export class ShopifyProductVariant { barcode: string compare_at_price: number created_at: Date fulfillment_service: string grams: number weight: number weight_unit: string id: string inventory_item_id: number inventory_management: string inventory_policy: string inventory_quantity: number option1: string position: number price: number product_id: number requires_shipping: boolean sku: string taxable: boolean title: string updated_at: Date } class Products { edges: ProductEdge[] pageInfo: PageInfo } class ProductEdge { node: ShopifyProduct variants?: ShopifyVariants options?: ShopifyProductOption[] cursor: string } export class ShopifyProductOption { id?: string name?: string optionValues?: ShopifyProductOptionValue[] values?: ShopifyProductOptionValue[] } export class ShopifyProductOptionValue { id?: string name?: string } export class ShopifyVariants { nodes?: ShopifyVariant[] node?: ShopifyVariant } export class ShopifyVariant { id?: string sku?: string price?: number compareAtPrice?: number barcode?: string position?: number metafields?: shopify.Metafield[] inventory_item?: shopify.InventoryItemInput nodes?: ShopifyProductVariant[] //TODO: support multiple options optionValues: [{}] = [ { optionName: "Title", name: "Default Title", }, ] } class PageInfo { hasNextPage: boolean hasPreviousPage: boolean startCursor: string endCursor: string } export class ShopifyProductsQuery { GQL: string JSON: JSON constructor( query: string = "", fields: string[] = ["id", "title", "handle"], cursor: string = "", pageSize: number = 10 ) { let cursorText: string if (cursor == "") { cursorText = "" } else { cursorText = `, after: "${cursor}"` } let queryText: string if (query == "") { queryText = "" } else { queryText = `, query: "${query}"` } this.GQL = /* GraphQL */ `{ products(first: ${pageSize}${cursorText}${queryText}) { edges { node { ${fields.join(" ")} variants(first:1) { nodes { id } } options { id name optionValues { id name } } } } pageInfo { hasNextPage endCursor } } }` let j = `{"query": ${formatGqlForJSON(this.GQL)}}` console.log(j) this.JSON = JSON.parse(j) } } export class ShopifyProductsResponse { products: Products constructor(response: {}) { this.products = response["data"]["products"] } } export class ShopifyProductSetQuery { GQL = /* GraphQL */ ` mutation setProduct($productSet: ProductSetInput!) { productSet(input: $productSet) { product { id } productSetOperation { id status userErrors { code field message } } userErrors { code field message } } } ` JSON: JSON constructor(product: ShopifyProductSetInput, synchronous: boolean = true) { let j = `{ "query": ${formatGqlForJSON(String(this.GQL))}, "variables": { "productSet": ${JSON.stringify(product)}, "synchronous": ${synchronous} } }` console.log(j) this.JSON = JSON.parse(j) } } export class ShopifyProductSetInput { category: string descriptionHtml: string handle: string id?: string productType: string redirectNewHandle: boolean = true status: string = "DRAFT" tags: string title: string vendor: string variants: ShopifyVariant[] metafields: shopify.MetafieldInput[] //TODO: Support multiple product options productOptions: [{}] = [ { name: "Title", values: { name: "Default Title", }, }, ] } export class ProductVariantSetInput { barcode: string compareAtPrice: number id: string optionValues: VariantOptionValueInput[] = [] position?: number price?: number requiresComponents?: boolean sku?: string taxable?: boolean taxCode?: string } export class VariantOptionValueInput { id?: string linkedMetafieldValue?: string name?: string optionId?: string optionName?: string } export function formatGqlForJSON(gql: string) { let singleLine = gql.split("\n").join(" ").replace(/\s+/g, " ") return JSON.stringify(singleLine) } export function runShopifyOrders() { let shop = new Shop() shop.RunOrders() } export function getShopifyProducts() { let shop = new Shop() shop.GetProducts() } export function buildGqlQuery(gql: string, variables: {}) { let query = `{ "query": ${formatGqlForJSON(String(gql))}, "variables": ${JSON.stringify(variables)} }` console.log("buildGqlQuery:\n" + query) return JSON.parse(query) }