{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "bundle-offers/function-config.schema.json", "title": "Bundle Offers - Discount Function configuration", "description": "Stored as JSON in the metafield $app:configuration on the ONE automatic app discount the app owns per shop. The app regenerates it whenever an offer, a plan, or a schedule boundary changes. Keep it compact: function metafields over ~10 kB are reportedly not delivered to functions (verify against current Shopify limits). Offers are listed in PRIORITY ORDER (lower sequence first); the function evaluates them in this order and every cart item counts towards at most one offer.", "type": "object", "required": [ "v", "offers" ], "additionalProperties": false, "properties": { "v": { "const": 1, "description": "Config format version." }, "cur": { "type": "string", "description": "Shop base currency (ISO 4217). 'amount' and 'fixed_price' values are in this currency; the function converts them with presentmentCurrencyRate." }, "ex": { "type": "object", "description": "Global exclusions: lines matching any of these never count towards a bundle.", "additionalProperties": false, "properties": { "tag": { "type": "string", "description": "Product tag that excludes a product, e.g. 'no-bundle' (Odoo: marketplace sellers' products)." }, "subs": { "type": "boolean", "default": true, "description": "Exclude lines bought with a selling plan (subscriptions)." }, "gc": { "type": "boolean", "default": true, "description": "Exclude gift cards." } } }, "offers": { "type": "array", "maxItems": 200, "items": { "$ref": "#/$defs/offer" } }, "ba": { "enum": [ "all", "get" ], "description": "Shopify addition: bxgy allocation. 'all' (default, Odoo parity) spreads over buy+get units; 'get' discounts only the get units." }, "g": { "type": "string", "format": "date-time", "description": "Shopify addition: guard time (UTC) = earliest 'e' of the listed offers. The app passes it, in shop local time, as the guardAt input variable; once shop.localTime is past it, the function drops offers with e <= g (covers a late scheduler)." } }, "$defs": { "gidList": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, "scope": { "type": "object", "description": "A cart item matches if its variant, OR its product (any variant), OR any of its collections is listed.", "additionalProperties": false, "properties": { "v": { "$ref": "#/$defs/gidList", "description": "Variant ids (numeric part of gid) " }, "p": { "$ref": "#/$defs/gidList", "description": "Product ids (any variant)" }, "c": { "$ref": "#/$defs/gidList", "description": "Collection ids (Odoo: eCommerce categories incl. sub-categories; Shopify collections are flat, so the merchant picks every collection that should count)" } } }, "offer": { "type": "object", "required": [ "id", "t", "n" ], "additionalProperties": false, "properties": { "id": { "type": "string", "description": "App offer id (short)." }, "n": { "type": "string", "maxLength": 80, "description": "Offer name; used as the discount message shown in cart/checkout." }, "t": { "enum": [ "fixed", "mix", "bxgy" ], "description": "Bundle type." }, "s": { "type": "string", "format": "date-time", "description": "Starts (UTC). Omitted = already started." }, "e": { "type": "string", "format": "date-time", "description": "Ends (UTC). Omitted = no end." }, "mu": { "type": "integer", "minimum": 0, "default": 0, "description": "Max uses per order, 0 = unlimited." }, "dt": { "enum": [ "percent", "amount", "fixed_price" ], "description": "fixed/mix only: % off, $ off, or bundle price." }, "dv": { "type": "number", "exclusiveMinimum": 0, "description": "fixed/mix only: the discount value (percent <= 100)." }, "l": { "type": "array", "minItems": 1, "description": "fixed only: bundle lines. Each line is a variant OR a product (any variant) with a quantity.", "items": { "type": "object", "additionalProperties": false, "required": [ "q" ], "properties": { "v": { "type": "string" }, "p": { "type": "string" }, "q": { "type": "integer", "minimum": 1 } }, "oneOf": [ { "required": [ "v" ] }, { "required": [ "p" ] } ] } }, "sc": { "$ref": "#/$defs/scope", "description": "mix only: eligible items." }, "q": { "type": "integer", "minimum": 1, "description": "mix only: number of items." }, "bs": { "$ref": "#/$defs/scope", "description": "bxgy only: what the customer buys." }, "bq": { "type": "integer", "minimum": 1, "description": "bxgy only: buy quantity." }, "gs": { "$ref": "#/$defs/scope", "description": "bxgy only: what the customer gets." }, "gq": { "type": "integer", "minimum": 1, "description": "bxgy only: get quantity." }, "gp": { "type": "number", "exclusiveMinimum": 0, "maximum": 100, "description": "bxgy only: % off the get items (100 = free)." }, "cc": { "type": "array", "items": { "type": "string", "pattern": "^[A-Z]{2}$" }, "description": "Shopify addition (F08): ISO country codes; the offer applies only when localization.country is listed. Empty/omitted = everywhere. (localization.market is deprecated in the function input.)" } }, "allOf": [ { "if": { "properties": { "t": { "const": "fixed" } } }, "then": { "required": [ "l", "dt", "dv" ] } }, { "if": { "properties": { "t": { "const": "mix" } } }, "then": { "required": [ "sc", "q", "dt", "dv" ] } }, { "if": { "properties": { "t": { "const": "bxgy" } } }, "then": { "required": [ "bs", "bq", "gs", "gq", "gp" ] } } ] } }, "examples": [ { "v": 1, "cur": "CAD", "ex": { "tag": "no-bundle", "subs": true, "gc": true }, "offers": [ { "id": "o1", "n": "Diwali Puja Kit", "t": "fixed", "dt": "percent", "dv": 20, "mu": 0, "s": "2026-10-20T04:00:00Z", "e": "2026-11-09T04:59:59Z", "l": [ { "v": "44001", "q": 1 }, { "p": "8001", "q": 2 } ] }, { "id": "o2", "n": "Any 3 Sweets for $20", "t": "mix", "dt": "fixed_price", "dv": 20, "q": 3, "sc": { "c": [ "4501", "4502" ] } }, { "id": "o3", "n": "Buy 2 Garlands, Get 1 Free", "t": "bxgy", "bq": 2, "gq": 1, "gp": 100, "bs": { "c": [ "4600" ] }, "gs": { "c": [ "4600" ] } } ] } ] }