
Product Bundles Kits
- 68 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Sell grouped products as bundles or kits with automatic inventory deduction, bundle pricing, and display logic using platform apps.
About
Sets up product bundles and kits with automatic component inventory deduction, bundle pricing, and storefront display logic. A developer uses it to sell grouped products while keeping stock accurate.
- Automatic inventory deduction for bundle components
- Bundle pricing and display logic via platform apps
Product Bundles Kits by the numbers
- 68 all-time installs (skills.sh)
- Ranked #3,096 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill product-bundles-kitsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 68 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Sell grouped products as bundles or kits with automatic inventory deduction, bundle pricing, and display logic using platform apps.
Files
Product Bundles & Kits
Overview
Product bundles let you sell multiple products together — optionally at a discount — as a single purchasable unit. This increases average order value and is one of the most effective upsell mechanisms in e-commerce. Dedicated bundle apps handle the inventory tracking, pricing, and display logic that platforms don't support natively. Only build a custom bundle system if your kit configuration requirements (dynamic assembly, real-time pricing, component substitution) exceed what apps offer.
When to Use This Skill
- When implementing a "Frequently Bought Together" or "Complete the Look" feature
- When selling product kits (e.g., a camera body + lens + bag as a bundle)
- When offering a discount for purchasing a set of products together
- When building a custom kit builder where shoppers assemble their own set from options
Core Instructions
Step 1: Determine platform and choose the right tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Bundler — Product Bundles or Bundle Builder | Bundler is the most popular; handles fixed bundles, mix-and-match, and inventory deduction per component |
| WooCommerce | WooCommerce Product Bundles (WooCommerce extension) | Official WooCommerce extension; supports fixed and configurable bundles, per-component pricing, and inventory |
| BigCommerce | Product Bundler app or native "Frequently Bought Together" | BigCommerce App Marketplace has several bundle apps; native "Frequently Bought Together" for simple cross-sells |
| Custom / Headless | Build bundle data model with component inventory deduction | Required when bundle logic (real-time pricing, dynamic component substitution) exceeds app capabilities |
---
Step 2: Platform-specific setup
---
Shopify
Option A: Bundler — Product Bundles (recommended)
1. Install Bundler — Product Bundles from the Shopify App Store 2. Go to Bundler → Bundles → Create bundle 3. Select bundle type:
- Fixed bundle: a predefined set of products (e.g., "Skincare Starter Kit")
- Mix & Match: shopper picks X items from a collection
- Build a Box: shopper fills a box with any combination
4. Add component products and set quantities 5. Set pricing:
- Percentage discount (e.g., 15% off the combined price)
- Fixed bundle price (e.g., $49.99 regardless of component prices)
- Sum of parts (no discount — just convenience bundling)
6. Bundler handles inventory: when a bundle is purchased, it decrements each component's stock automatically
For "Frequently Bought Together":
- Install Frequently Bought Together by Code Black Belt (App Store)
- The app analyzes your order history and automatically suggests products to bundle on the PDP
- Works out of the box with no manual configuration
Important for Shopify Plus:
- For bundles that must appear as a single line item in the cart (for loyalty points, discount exclusions, etc.), consider Shopify Bundles (native, available in admin under Products → Bundles)
- Shopify's native Bundles product type creates a parent SKU that the buyer purchases, with automatic inventory deduction of components
---
WooCommerce
WooCommerce Product Bundles (official extension):
1. Install WooCommerce Product Bundles from WooCommerce.com 2. Create a new product: Products → Add Product → Product type: Bundle 3. Go to the Bundled Products tab 4. Add each component:
- Search and select the product
- Set the default quantity and whether it's optional (for configurable bundles)
- Enable Ship separately if components ship from different locations
5. Set bundle pricing under the General tab:
- Per-item pricing: bundle price = sum of component prices (with optional discount)
- Static bundle pricing: enter a fixed price for the bundle
6. Inventory: enable Manage stock? if you want to cap how many bundles can be sold (based on component availability)
For "Frequently Bought Together":
- Install WooCommerce Frequently Bought Together (free or paid versions available)
- Or use YITH WooCommerce Frequently Bought Together
---
BigCommerce
Bundle apps from the App Marketplace:
1. Search for "Bundle" in the Apps section of your BigCommerce admin 2. Bold Bundles is widely used — install and configure bundle products linking to existing SKUs 3. Set discount rules (percent off, fixed price, BOGO) 4. Bold Bundles handles inventory decrement for each component automatically
Native cross-sell / "Frequently Bought Together": 1. Go to Products → [Product] → Related Products tab 2. Add related products — these appear as suggestions on the PDP 3. No automatic discount, but customers can add individual items
---
Custom / Headless
For headless storefronts, build a bundle model where components are stored as separate cart line items (simplifies inventory, tax, and fulfillment):
// lib/bundles.ts
interface BundleComponent { variantId: string; quantity: number; unitPrice: number; }
interface BundlePricing { componentSum: number; bundlePrice: number; savings: number; savingsPct: number; }
// Calculate bundle price dynamically from current component prices
export async function calculateBundlePrice(bundle: Bundle, selectedVariants: BundleComponent[]): Promise<BundlePricing> {
const componentSum = selectedVariants.reduce((total, c) => total + c.unitPrice * c.quantity, 0);
let bundlePrice: number;
switch (bundle.pricingType) {
case 'fixed': bundlePrice = bundle.pricingValue; break;
case 'discount_pct': bundlePrice = +(componentSum * (1 - bundle.pricingValue / 100)).toFixed(2); break;
case 'discount_abs': bundlePrice = Math.max(0, +(componentSum - bundle.pricingValue).toFixed(2)); break;
default: bundlePrice = componentSum;
}
const savings = +(componentSum - bundlePrice).toFixed(2);
return { componentSum, bundlePrice, savings, savingsPct: componentSum > 0 ? Math.round((savings / componentSum) * 100) : 0 };
}
// Check availability for all bundle components atomically
export async function checkBundleAvailability(selectedVariants: BundleComponent[]) {
const unavailable = [];
for (const { variantId, quantity } of selectedVariants) {
const level = await db.inventoryLevels.findFirst({ where: { variantId } });
const available = (level?.onHand ?? 0) - (level?.reserved ?? 0);
if (available < quantity) {
unavailable.push({ variantId, requested: quantity, available: Math.max(0, available) });
}
}
return { available: unavailable.length === 0, unavailable };
}
// Add bundle to cart as separate line items (grouped by bundle ID for UI display)
export async function addBundleToCart(cartId: string, bundleId: string, selectedVariants: BundleComponent[]) {
const bundle = await db.productBundles.findUnique({ where: { id: bundleId } });
const pricing = await calculateBundlePrice(bundle, selectedVariants);
const { available, unavailable } = await checkBundleAvailability(selectedVariants);
if (!available) throw new Error(`Bundle not available: ${unavailable.map(u => u.variantId).join(', ')}`);
// Create a bundle group record to keep line items visually associated
const bundleGroup = await db.cartBundleGroups.create({
data: { cartId, bundleId, bundlePrice: pricing.bundlePrice, bundleSavings: pricing.savings },
});
// Pro-rate the discount across components proportionally to their share of the total
const lineItems = selectedVariants.map(({ variantId, quantity, unitPrice }) => {
const itemSubtotal = unitPrice * quantity;
const discountShare = pricing.savings * (itemSubtotal / pricing.componentSum);
const discountedUnitPrice = +(unitPrice - discountShare / quantity).toFixed(4);
return { cartId, variantId, quantity, bundleGroupId: bundleGroup.id, unitPrice: discountedUnitPrice };
});
await db.cartItems.createMany({ data: lineItems });
return bundleGroup;
}---
Step 3: Configure bundle display on product pages
Bundle display checklist:
- Show "Value: $149 | Bundle price: $119 — Save $30 (20% off)" to make the discount tangible
- Show which items are included with product images and names
- Show availability: if any component is out of stock, show which item and suggest a substitute
- On the cart page, group bundle line items under a visual header with the bundle name and total savings badge
For Shopify/Bundler: The app generates a bundle product page automatically — customize the template in Bundler → Customize
For WooCommerce Product Bundles: The plugin renders a bundle-specific product page layout; style it using the built-in CSS settings or child theme overrides
---
Step 4: Verify inventory deduction is working
After setting up bundles, test that inventory deducts correctly for each component:
1. Place a test order for a bundle 2. Check inventory levels for each component product — each should have decremented by the bundle component quantity 3. Test the edge case: a bundle where one component has exactly 1 unit remaining; ordering 2 bundles should fail on the second
Best Practices
- Use apps instead of building from scratch — Bundler for Shopify and WooCommerce Product Bundles handle edge cases (split fulfillment, partial availability, refunds) that take weeks to build correctly
- Always validate bundle availability server-side — check that all components have sufficient stock atomically before adding to cart; a bundle is only purchasable if every component is in stock
- Recalculate bundle pricing at checkout — never trust client-submitted bundle prices; recalculate from current component prices at order time
- Show per-item and bundle prices together — the discount is only meaningful when customers can see what they're saving against
- Keep bundles to 2–5 components — larger bundles confuse shoppers and are harder to merchandise clearly
Common Pitfalls
| Problem | Solution |
|---|---|
| Bundle discount applied incorrectly at checkout | Use the app's built-in discount logic; don't layer additional discount codes on top of bundle discounts without testing the interaction |
| One bundle component goes out of stock mid-cart | Re-check availability at checkout; display which specific component is unavailable so the customer can adjust |
| Bundle pricing stale when component prices change | Recalculate bundle price on each cart refresh and at checkout, not just at add-to-cart time |
| Fulfillment system confused by bundle line items | For headless builds, ensure each line item has a standard variant_id and quantity; use bundle_group_id only for UI grouping |
| Inventory not decremented for all bundle components | After setup, always test with a real order and verify each component SKU decremented by the correct quantity |
Related Skills
- @variant-matrix
- @inventory-tracking
- @product-content-enrichment
{
"context": "Tests whether the agent correctly implements bundle cart logic: storing bundles as individual line items with a group record, pro-rating discounts across components, and validating availability before adding to cart.",
"type": "weighted_checklist",
"checklist": [
{
"name": "separate line items",
"max_score": 10,
"description": "Each bundle component is added as an individual cart line item, NOT as a single composite/bundle item"
},
{
"name": "bundle group record",
"max_score": 10,
"description": "A separate bundle group record (e.g. cart_bundle_groups or equivalent) is created to associate the line items, storing bundlePrice and bundleSavings (or equivalent)"
},
{
"name": "group id on line items",
"max_score": 10,
"description": "Each component line item is tagged with a bundleGroupId (or equivalent foreign key) linking it to the bundle group record"
},
{
"name": "pro-rated discount",
"max_score": 10,
"description": "Each component's unit price is discounted proportionally based on its share of the total component sum (not applying equal discount to all items)"
},
{
"name": "pro-rate formula",
"max_score": 8,
"description": "Pro-rating uses the formula: discountShare = savings * (itemSubtotal / componentSum), where itemSubtotal = unitPrice * quantity"
},
{
"name": "availability check before add",
"max_score": 10,
"description": "Bundle availability is checked (all components have sufficient stock) before the cart items are created"
},
{
"name": "available = onHand minus reserved",
"max_score": 8,
"description": "Available stock is computed as onHand minus reserved (not just onHand alone)"
},
{
"name": "unavailable component detail",
"max_score": 8,
"description": "When a component is unavailable, the response or error identifies which specific component (by variantId or product name) is out of stock"
},
{
"name": "standard variant_id and quantity",
"max_score": 8,
"description": "Each cart line item has a standard variant_id and quantity field; bundle_group_id is not used for fulfillment logic"
},
{
"name": "server-side pricing recalc",
"max_score": 10,
"description": "Bundle price is recalculated server-side during add-to-cart, NOT using a client-provided price value"
},
{
"name": "demo output",
"max_score": 8,
"description": "A runnable demo or test script exists that exercises both a successful add-to-cart and an unavailable-stock scenario"
}
]
}
Bundle Add-to-Cart Service
Problem/Feature Description
An outdoor gear retailer offers product bundles — for example, a "Camping Essentials" bundle containing a tent, sleeping bag, and camp stove at a discounted price. When a customer clicks "Add Bundle to Cart," the system needs to correctly record all the items, track the discount, and ensure everything is actually in stock.
The backend team is building a cart service module responsible for handling bundle additions. A key requirement from the fulfillment team is that each product in a bundle must remain a standard, independently trackable line item in the cart so it can be picked, packed, and shipped normally. At the same time, the finance team needs to know how the bundle discount was distributed across items for tax and accounting purposes. The service must also block shoppers from adding a bundle when any component lacks sufficient inventory, and it must clearly communicate which component caused the problem.
Output Specification
Implement a JavaScript (or TypeScript) module in cartBundles.js (or cartBundles.ts) that exports an add-to-cart function for bundles. Use the in-memory data provided below to simulate database state (products, inventory levels, and cart).
Also produce a demo.js script that runs at least two scenarios: 1. A successful bundle addition where all items are in stock 2. A failed addition where one or more components have insufficient inventory
The demo should print the resulting cart state (line items, bundle group records) and any error/unavailability details to stdout. Include a short README.md with run instructions.
Input Files
Extract the following files before beginning.
=============== FILE: inputs/catalog.json =============== { "variants": [ { "id": "var_tent", "productId": "prod_tent", "name": "2-Person Tent", "price": 189.00 }, { "id": "var_sleeping_bag", "productId": "prod_sleeping_bag", "name": "20°F Sleeping Bag", "price": 129.00 }, { "id": "var_stove", "productId": "prod_stove", "name": "Backpacking Stove", "price": 69.00 }, { "id": "var_headlamp", "productId": "prod_headlamp", "name": "LED Headlamp", "price": 45.00 }, { "id": "var_trekking_poles", "productId": "prod_poles", "name": "Trekking Poles (pair)", "price": 89.00 } ], "inventory": [ { "variantId": "var_tent", "onHand": 10, "reserved": 2 }, { "variantId": "var_sleeping_bag", "onHand": 5, "reserved": 4 }, { "variantId": "var_stove", "onHand": 20, "reserved": 1 }, { "variantId": "var_headlamp", "onHand": 3, "reserved": 3 }, { "variantId": "var_trekking_poles", "onHand": 7, "reserved": 1 } ] }
=============== FILE: inputs/bundles.json =============== { "bundles": [ { "id": "bundle_camping_essentials", "name": "Camping Essentials", "bundle_type": "fixed", "pricing_type": "discount_pct", "pricing_value": 15, "components": [ { "variantId": "var_tent", "quantity": 1, "isRequired": true }, { "variantId": "var_sleeping_bag", "quantity": 1, "isRequired": true }, { "variantId": "var_stove", "quantity": 1, "isRequired": true } ] }, { "id": "bundle_headlamp_kit", "name": "Light & Pole Kit", "bundle_type": "fixed", "pricing_type": "discount_abs", "pricing_value": 20, "components": [ { "variantId": "var_headlamp", "quantity": 2, "isRequired": true }, { "variantId": "var_trekking_poles", "quantity": 1, "isRequired": true } ] } ] }
{
"context": "Tests whether the agent correctly implements the bundle product display page and cart bundle UI, including pricing display with savings, default variant selection, and the cart group layout with savings badge.",
"type": "weighted_checklist",
"checklist": [
{
"name": "component sum displayed",
"max_score": 10,
"description": "The bundle PDP displays the total component sum (original value) as well as the final bundle price, not just the bundle price alone"
},
{
"name": "savings amount displayed",
"max_score": 8,
"description": "The PDP shows the savings amount (componentSum minus bundlePrice) when there is a discount"
},
{
"name": "savings percentage displayed",
"max_score": 8,
"description": "The PDP shows the savings percentage when there is a discount (e.g. '20% off' or 'Save 20%')"
},
{
"name": "first variant pre-selected",
"max_score": 10,
"description": "Required components with no fixed variant_id have the first available option pre-selected by default in the UI state"
},
{
"name": "required component check",
"max_score": 8,
"description": "Add-to-cart is only enabled (or pricing is only shown) when all required components have a selection"
},
{
"name": "cart bundle group header",
"max_score": 8,
"description": "In the cart display, bundled items are shown under a group header that includes the bundle name"
},
{
"name": "savings badge in cart",
"max_score": 10,
"description": "The cart bundle group header shows a savings badge/indicator with the savings amount when bundleSavings > 0"
},
{
"name": "bundle indicator on items",
"max_score": 8,
"description": "Individual cart line items within a bundle group show a bundle indicator (e.g. showBundleIndicator prop or equivalent visual marker)"
},
{
"name": "partial availability message",
"max_score": 10,
"description": "When a component is out of stock, the UI shows a message identifying which specific component is unavailable (not just a generic 'out of stock' message)"
},
{
"name": "no large files",
"max_score": 8,
"description": "No downloaded assets, images, or files larger than 50MB are left in the workspace"
},
{
"name": "pricing updated on selection change",
"max_score": 8,
"description": "For dynamic kits, the bundle price is recalculated (or a fetch is triggered) when the shopper changes a variant selection"
},
{
"name": "dynamic kit group name",
"max_score": 4,
"description": "For dynamic kit components, a group_name or label is shown to identify each selection slot (e.g. 'Camera Body', 'Lens')"
}
]
}
Bundle Product Page and Cart Display
Problem/Feature Description
A home goods retailer sells curated product sets — for example, a "Bedroom Refresh Kit" that lets shoppers pick from several mattress sizes paired with a pre-selected comforter and pillow set. When a shopper lands on the bundle's product page, they need to see clearly how much they would spend buying the items separately versus the bundle price, and the savings should be highlighted. When a component the shopper wants is out of stock, the page should tell them specifically which item isn't available.
The front-end team also wants the shopping cart to feel cohesive: bundle items should appear visually grouped under the bundle name, with a clear savings indicator, so shoppers understand what they bought and why the prices look the way they do. The team needs a set of React components that handle both the bundle product detail page and the cart bundle group view, along with a working demo that exercises the full flow including a scenario where a component is out of stock.
Output Specification
Produce React components (.jsx or .tsx) for:
- A bundle product detail page component
- A cart bundle group display component
Also produce a demo.jsx (or App.jsx) that renders both components with the provided sample data in at least two states: 1. All components available (with a dynamic kit variant that can be switched) 2. One component out of stock
Use a bundler/runner that can be started with a standard command (e.g. npm start, npx vite, or render to a static HTML file). Include a README.md explaining how to run it.
If a full browser environment is too complex to set up, producing the components plus a Node.js script that renders them to an HTML string and writes output.html is also acceptable.
Input Files
Extract the following files before beginning.
=============== FILE: inputs/bundle-data.json =============== { "bundle": { "id": "bundle_bedroom_refresh", "name": "Bedroom Refresh Kit", "bundle_type": "dynamic", "pricing_type": "discount_pct", "pricing_value": 12, "components": [ { "id": "comp_mattress", "group_name": "Mattress", "is_required": true, "variant_id": null, "quantity": 1, "selectable_products": [ { "id": "prod_queen_mattress", "name": "Queen Mattress", "variants": [ { "id": "var_queen_plush", "name": "Plush", "price": 599.00 }, { "id": "var_queen_firm", "name": "Firm", "price": 629.00 } ] }, { "id": "prod_king_mattress", "name": "King Mattress", "variants": [ { "id": "var_king_plush", "name": "Plush", "price": 749.00 }, { "id": "var_king_firm", "name": "Firm", "price": 789.00 } ] } ] }, { "id": "comp_comforter", "group_name": "Comforter", "is_required": true, "variant_id": "var_down_comforter", "quantity": 1, "selectable_products": [] }, { "id": "comp_pillows", "group_name": "Pillow Set", "is_required": true, "variant_id": "var_pillow_set_2pk", "quantity": 1, "selectable_products": [] } ] }, "variants": { "var_queen_plush": { "id": "var_queen_plush", "name": "Queen Plush Mattress", "price": 599.00 }, "var_queen_firm": { "id": "var_queen_firm", "name": "Queen Firm Mattress", "price": 629.00 }, "var_king_plush": { "id": "var_king_plush", "name": "King Plush Mattress", "price": 749.00 }, "var_king_firm": { "id": "var_king_firm", "name": "King Firm Mattress", "price": 789.00 }, "var_down_comforter":{ "id": "var_down_comforter","name": "Down Comforter", "price": 149.00 }, "var_pillow_set_2pk":{ "id": "var_pillow_set_2pk","name": "Pillow Set (2-pack)", "price": 79.00 } }, "inventory": { "var_queen_plush": { "onHand": 8, "reserved": 1 }, "var_queen_firm": { "onHand": 5, "reserved": 0 }, "var_king_plush": { "onHand": 2, "reserved": 2 }, "var_king_firm": { "onHand": 3, "reserved": 0 }, "var_down_comforter": { "onHand": 0, "reserved": 0 }, "var_pillow_set_2pk": { "onHand": 12, "reserved": 2 } }, "sampleCartBundleGroup": { "id": "cbg_001", "bundleId": "bundle_bedroom_refresh", "bundleName": "Bedroom Refresh Kit", "bundlePrice": 730.56, "bundleSavings": 99.44, "items": [ { "id": "li_001", "variantId": "var_queen_plush", "name": "Queen Plush Mattress", "unitPrice": 520.50, "quantity": 1, "bundleGroupId": "cbg_001" }, { "id": "li_002", "variantId": "var_down_comforter", "name": "Down Comforter", "unitPrice": 129.48, "quantity": 1, "bundleGroupId": "cbg_001" }, { "id": "li_003", "variantId": "var_pillow_set_2pk", "name": "Pillow Set (2-pack)", "unitPrice": 80.58, "quantity": 1, "bundleGroupId": "cbg_001" } ] } }
{
"context": "Tests whether the agent implements bundle pricing logic correctly, covering all four pricing types, proper savings calculations, and server-side price calculation patterns.",
"type": "weighted_checklist",
"checklist": [
{
"name": "sum pricing type",
"max_score": 8,
"description": "Implements 'sum' pricing type that returns the total of all component prices as the bundle price"
},
{
"name": "fixed pricing type",
"max_score": 8,
"description": "Implements 'fixed' pricing type that returns a fixed dollar amount regardless of component prices"
},
{
"name": "discount_pct pricing type",
"max_score": 8,
"description": "Implements 'discount_pct' pricing type that applies a percentage discount to the component sum (e.g. sumPrice * (1 - value/100))"
},
{
"name": "discount_abs pricing type",
"max_score": 8,
"description": "Implements 'discount_abs' pricing type that subtracts a fixed amount from the component sum, floored at 0"
},
{
"name": "componentSum returned",
"max_score": 8,
"description": "The function returns a 'componentSum' (or equivalent) representing the total of all individual component prices"
},
{
"name": "savings returned",
"max_score": 8,
"description": "The function returns 'savings' (or equivalent) as the difference between componentSum and bundlePrice"
},
{
"name": "savingsPct returned",
"max_score": 8,
"description": "The function returns 'savingsPct' (or equivalent) as the percentage of savings relative to componentSum, rounded to an integer"
},
{
"name": "current prices fetched",
"max_score": 10,
"description": "Prices are fetched from a data source (DB, API, or input data) rather than using client-submitted or hardcoded prices"
},
{
"name": "toFixed precision",
"max_score": 8,
"description": "Bundle price for discount_pct and discount_abs uses toFixed(2) or equivalent rounding to 2 decimal places"
},
{
"name": "price per variant and quantity",
"max_score": 8,
"description": "Component sum accounts for quantity (price * quantity per component), not just unit price"
},
{
"name": "tests cover all pricing types",
"max_score": 10,
"description": "Test cases (or demo inputs) exercise all four pricing_type values (sum, fixed, discount_pct, discount_abs)"
},
{
"name": "no client price trust",
"max_score": 8,
"description": "Pricing function does NOT accept a pre-calculated price as a direct input — it computes price from component data"
}
]
}
Bundle Pricing Engine
Problem/Feature Description
A retail platform sells product bundles — curated sets of items offered at various price points. The merchandising team needs flexible control over how bundles are priced: sometimes a bundle should cost exactly whatever its components add up to, sometimes it should be sold at a flat rate, and sometimes they want to offer a percentage or dollar-amount discount off the component total. The same pricing engine must be reliable for all of these cases.
The platform's engineering team is building a standalone pricing utility that can be called server-side whenever a shopper views or adds a bundle. The utility must accept a bundle configuration and a list of selected product variants (with quantities), look up the current prices for those variants, and produce a complete pricing breakdown. The output needs to power the display of both the bundle's final price and the savings a shopper receives, so all relevant figures must be returned.
Output Specification
Produce a self-contained JavaScript (or TypeScript) module in a file called bundlePricing.js (or bundlePricing.ts) that exports a pricing function. The module should work with the sample data provided below — you may use in-memory data structures to simulate a database lookup.
Also produce a demo.js (or equivalent) script that calls the function with test inputs covering all the different ways a bundle can be priced, and writes the results to stdout. Running node demo.js (or equivalent) should print the pricing output for each case.
Include a short README.md explaining how to run the demo.
Input Files
The following data represents your product catalog and bundle configurations. Extract them before beginning.
=============== FILE: inputs/products.json =============== { "variants": [ { "id": "var_camera_body", "productId": "prod_camera", "name": "Sony A6000 Body", "price": 499.00 }, { "id": "var_lens_kit", "productId": "prod_lens", "name": "16-50mm Kit Lens", "price": 149.00 }, { "id": "var_bag", "productId": "prod_bag", "name": "Camera Bag", "price": 59.00 }, { "id": "var_tripod", "productId": "prod_tripod", "name": "Travel Tripod", "price": 79.00 }, { "id": "var_memory_card", "productId": "prod_card", "name": "64GB SD Card", "price": 25.00 } ] }
=============== FILE: inputs/bundles.json =============== { "bundles": [ { "id": "bundle_sum", "name": "Starter Set (Sum Pricing)", "bundle_type": "fixed", "pricing_type": "sum", "pricing_value": null, "components": [ { "variant_id": "var_camera_body", "quantity": 1 }, { "variant_id": "var_memory_card", "quantity": 2 } ] }, { "id": "bundle_fixed", "name": "Lens + Bag Bundle (Fixed Price)", "bundle_type": "fixed", "pricing_type": "fixed", "pricing_value": 179.00, "components": [ { "variant_id": "var_lens_kit", "quantity": 1 }, { "variant_id": "var_bag", "quantity": 1 } ] }, { "id": "bundle_discount_pct", "name": "Camera Kit (10% Off)", "bundle_type": "fixed", "pricing_type": "discount_pct", "pricing_value": 10, "components": [ { "variant_id": "var_camera_body", "quantity": 1 }, { "variant_id": "var_lens_kit", "quantity": 1 }, { "variant_id": "var_bag", "quantity": 1 } ] }, { "id": "bundle_discount_abs", "name": "Travel Kit ($50 Off)", "bundle_type": "fixed", "pricing_type": "discount_abs", "pricing_value": 50, "components": [ { "variant_id": "var_camera_body", "quantity": 1 }, { "variant_id": "var_tripod", "quantity": 1 }, { "variant_id": "var_memory_card", "quantity": 2 } ] } ] }
{
"name": "finsi/product-bundles-kits",
"version": "0.1.0",
"summary": "Bundle/kit management with dynamic pricing, inventory deduction, and display logic",
"skills": {
"product-bundles-kits": {
"path": "SKILL.md"
}
}
}