> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synapsebuilder.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Discount Functions

> Build custom discount logic with Shopify Functions

## Overview

Discount Functions let you create custom discount logic beyond Shopify's built-in discount types. Volume discounts, bundle pricing, tiered discounts, and customer-specific pricing.

## How Discount Functions Work

<Steps>
  <Step title="Customer Action">
    Customer adds items to cart or enters checkout
  </Step>

  <Step title="Function Execution">
    Your function receives cart data and evaluates discount logic
  </Step>

  <Step title="Discount Applied">
    Function returns discount configuration
  </Step>

  <Step title="Price Updated">
    Shopify applies discount and updates cart/checkout
  </Step>
</Steps>

## Discount Types

<CardGroup cols={2}>
  <Card title="Volume Discounts" icon="layer-group">
    Discount based on quantity

    * Buy 2 get 10% off
    * Buy 5 get 20% off
    * Tiered pricing
  </Card>

  <Card title="Bundle Discounts" icon="box-open">
    Discount on product combinations

    * Buy X + Y get Z free
    * Complete the set discount
    * Mix and match deals
  </Card>

  <Card title="Customer-Specific" icon="user-tag">
    Personalized pricing

    * VIP customer discounts
    * Loyalty tier pricing
    * Wholesale pricing
  </Card>

  <Card title="Conditional Discounts" icon="filter">
    Context-based pricing

    * First-time buyer discount
    * Time-based promotions
    * Location-based pricing
  </Card>
</CardGroup>

## Example: Volume Discount

Generate a function that gives tiered discounts:

<Steps>
  <Step title="Describe Your Function">
    ```
    Create a discount function that gives 10% off when buying 
    2-4 units of the same product, 15% off for 5-9 units, 
    and 20% off for 10 or more units. Show the discount in 
    the cart and checkout.
    ```
  </Step>

  <Step title="Review Generated Code">
    Synapse creates:

    * Rust or JavaScript function
    * Input query for cart data
    * Discount calculation logic
    * Output configuration
    * Extension UI (optional)
  </Step>

  <Step title="Deploy & Test">
    * Automatic validation
    * Deployed to dev store
    * Test with different quantities
  </Step>

  <Step title="Activate Function">
    1. Go to Discounts in admin
    2. Create new discount using your function
    3. Configure settings
    4. Set active dates
  </Step>
</Steps>

## Generated Function Structure

```
extensions/volume-discount/
├── src/
│   ├── run.rs                 # Function logic (Rust)
│   │   OR
│   ├── run.js                 # Function logic (JS)
│   └── run.graphql           # Input query
├── shopify.extension.toml     # Config
├── Cargo.toml                 # Dependencies (Rust)
│   OR
└── package.json               # Dependencies (JS)
```

## Function Code Examples

<Tabs>
  <Tab title="Rust (Recommended)">
    Rust functions are faster and more efficient:

    ```rust theme={null}
    use shopify_function::prelude::*;
    use shopify_function::Result;

    #[shopify_function_target(query_path = "src/run.graphql", schema_path = "schema.graphql")]
    fn run(input: input::ResponseData) -> Result<output::FunctionRunResult> {
        let discounts = input
            .cart
            .lines
            .iter()
            .filter_map(|line| {
                let quantity = line.quantity;
                
                let percentage = match quantity {
                    2..=4 => 10.0,
                    5..=9 => 15.0,
                    10.. => 20.0,
                    _ => return None,
                };
                
                Some(output::Discount {
                    message: Some(format!("{}% volume discount", percentage)),
                    targets: vec![output::Target {
                        product_variant: Some(output::ProductVariantTarget {
                            id: line.merchandise.id.clone(),
                            quantity: None,
                        }),
                    }],
                    value: output::Value::Percentage(output::Percentage {
                        value: Decimal::from(percentage),
                    }),
                })
            })
            .collect();
        
        Ok(output::FunctionRunResult { discounts })
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    JavaScript functions are easier to modify:

    ```javascript theme={null}
    export function run(input) {
      const discounts = input.cart.lines
        .map(line => {
          const quantity = line.quantity;
          
          let percentage;
          if (quantity >= 10) {
            percentage = 20;
          } else if (quantity >= 5) {
            percentage = 15;
          } else if (quantity >= 2) {
            percentage = 10;
          } else {
            return null;
          }
          
          return {
            message: `${percentage}% volume discount`,
            targets: [{
              productVariant: {
                id: line.merchandise.id
              }
            }],
            value: {
              percentage: {
                value: percentage.toString()
              }
            }
          };
        })
        .filter(discount => discount !== null);
      
      return { discounts };
    }
    ```
  </Tab>

  <Tab title="Input Query">
    GraphQL query defines function input:

    ```graphql theme={null}
    query RunInput {
      cart {
        lines {
          id
          quantity
          merchandise {
            __typename
            ... on ProductVariant {
              id
              product {
                id
                title
                hasAnyTag(tags: ["volume-discount"])
              }
            }
          }
        }
      }
    }
    ```
  </Tab>
</Tabs>

## Common Discount Patterns

<AccordionGroup>
  <Accordion title="Buy X Get Y" icon="gift">
    Buy one product, get another free/discounted:

    ```rust theme={null}
    // Check if customer has product X
    let has_product_x = input.cart.lines.iter()
        .any(|line| line.merchandise.product.id == "gid://shopify/Product/123");

    if has_product_x {
        // Apply discount to product Y
        discounts.push(Discount {
            targets: vec![Target {
                product_variant: Some(ProductVariantTarget {
                    id: "gid://shopify/ProductVariant/456".to_string(),
                }),
            }],
            value: Value::Percentage(Percentage { value: Decimal::from(100) }),
            message: Some("Free with purchase!".to_string()),
        });
    }
    ```

    **Prompt Example:**

    ```
    "Create a discount function where if customers buy any t-shirt 
    (tagged 'tshirt'), they get 50% off any socks (tagged 'socks')"
    ```
  </Accordion>

  <Accordion title="Spend Threshold" icon="dollar-sign">
    Discount when cart total reaches threshold:

    ```rust theme={null}
    use rust_decimal::Decimal;

    let cart_total: Decimal = input.cart.lines.iter()
        .map(|line| {
            let price = Decimal::from_str(&line.merchandise.price).unwrap();
            price * Decimal::from(line.quantity)
        })
        .sum();

    if cart_total >= Decimal::from(100) {
        // Apply 10% off entire cart
        discounts.push(Discount {
            targets: vec![Target::OrderSubtotal],
            value: Value::Percentage(Percentage { 
                value: Decimal::from(10) 
            }),
            message: Some("10% off orders over $100".to_string()),
        });
    }
    ```

    **Prompt Example:**

    ```
    "Create a discount that gives 15% off the entire order when 
    the subtotal is $150 or more"
    ```
  </Accordion>

  <Accordion title="Customer Segment" icon="users">
    Discount for specific customer groups:

    ```rust theme={null}
    // Check customer tags
    let is_vip = input.cart.buyer_identity
        .as_ref()
        .and_then(|identity| identity.customer.as_ref())
        .map(|customer| customer.has_any_tag(vec!["vip"]))
        .unwrap_or(false);

    if is_vip {
        // 20% off for VIP customers
        discounts.push(Discount {
            targets: vec![Target::OrderSubtotal],
            value: Value::Percentage(Percentage { 
                value: Decimal::from(20) 
            }),
            message: Some("VIP member discount".to_string()),
        });
    }
    ```

    **Prompt Example:**

    ```
    "Create a discount function that gives 25% off to customers 
    tagged 'wholesale' on all products in the 'bulk' collection"
    ```
  </Accordion>

  <Accordion title="Bundle Builder" icon="boxes-stacked">
    Discount for buying complete sets:

    ```rust theme={null}
    let required_products = vec![
        "gid://shopify/Product/1",
        "gid://shopify/Product/2",
        "gid://shopify/Product/3",
    ];

    let has_all = required_products.iter().all(|product_id| {
        input.cart.lines.iter()
            .any(|line| line.merchandise.product.id == *product_id)
    });

    if has_all {
        // 30% off when buying complete bundle
        discounts.push(Discount {
            targets: required_products.iter().map(|id| {
                Target::ProductVariant(/* ... */)
            }).collect(),
            value: Value::Percentage(Percentage { 
                value: Decimal::from(30) 
            }),
            message: Some("Bundle discount!".to_string()),
        });
    }
    ```

    **Prompt Example:**

    ```
    "Create a bundle discount where if customers buy a camera, 
    lens, and memory card together, they get 25% off the total 
    for those three items"
    ```
  </Accordion>
</AccordionGroup>

## Function Configuration

Functions can have configurable settings:

```toml theme={null}
# shopify.extension.toml
[[extensions.settings]]
name = "min_quantity"
type = "number_integer"
description = "Minimum quantity for discount"

[[extensions.settings]]
name = "discount_percentage"
type = "number_decimal"
description = "Discount percentage to apply"

[[extensions.settings]]
name = "eligible_products"
type = "product_reference"
description = "Products eligible for discount"
```

Access settings in your function:

```rust theme={null}
let min_quantity = input.discount_node.metafield
    .as_ref()
    .and_then(|m| m.value.parse::<i64>().ok())
    .unwrap_or(2);

let discount_percentage = input.discount_node.metafield
    .as_ref()
    .and_then(|m| m.value.parse::<f64>().ok())
    .unwrap_or(10.0);
```

## Testing Discount Functions

<Steps>
  <Step title="Create Discount in Admin">
    1. Go to Discounts
    2. Create → Discount
    3. Select your function
    4. Configure settings
  </Step>

  <Step title="Test in Storefront">
    * Add eligible products to cart
    * Verify discount appears
    * Check discount amount is correct
    * Test edge cases
  </Step>

  <Step title="Review Logs">
    Check function execution logs:

    ```bash theme={null}
    shopify app function logs
    ```

    View input/output for each execution
  </Step>

  <Step title="Performance Testing">
    * Test with many cart items
    * Measure execution time
    * Verify under 5ms threshold
  </Step>
</Steps>

## Performance Best Practices

Discount functions must execute in under 5ms:

<CardGroup cols={2}>
  <Card title="Minimize Loops" icon="arrows-rotate">
    Avoid nested loops over cart items

    ```rust theme={null}
    // Good: Single pass
    let total = lines.iter().map(|l| l.price).sum();

    // Bad: Nested loops
    for line in &lines {
        for other in &lines {
            // ...
        }
    }
    ```
  </Card>

  <Card title="Use Rust" icon="gear">
    Rust functions are 10-100x faster than JavaScript

    * Compiled to WebAssembly
    * No garbage collection
    * Type-safe
    * Recommended for production
  </Card>

  <Card title="Cache Calculations" icon="memory">
    Don't recalculate the same value

    ```rust theme={null}
    // Calculate once
    let cart_total = calculate_total(&input.cart);

    // Reuse
    if cart_total > threshold {
        // ...
    }
    ```
  </Card>

  <Card title="Limit API Surface" icon="filter">
    Only query data you need in run.graphql

    ```graphql theme={null}
    # Good: Specific fields
    query RunInput {
      cart {
        lines {
          quantity
          merchandise { id }
        }
      }
    }
    ```
  </Card>
</CardGroup>

## Debugging Functions

<Tabs>
  <Tab title="Local Testing">
    Test functions locally before deploying:

    ```bash theme={null}
    cd extensions/discount-function

    # Test with sample input
    shopify app function run

    # Provide test input
    cat test-input.json | shopify app function run
    ```
  </Tab>

  <Tab title="Live Logs">
    View function executions in real-time:

    ```bash theme={null}
    shopify app function logs --watch
    ```

    Shows:

    * Input received
    * Output generated
    * Execution time
    * Errors
  </Tab>

  <Tab title="Error Handling">
    Functions should never panic:

    ```rust theme={null}
    // Good: Handle errors
    let quantity = line.quantity.parse::<i64>()
        .unwrap_or(0);

    // Bad: Panic on error
    let quantity = line.quantity.parse::<i64>()
        .expect("quantity must be number");
    ```
  </Tab>
</Tabs>

## Limitations

Discount Functions have certain restrictions:

| Limitation           | Details                          |
| -------------------- | -------------------------------- |
| **Execution Time**   | Must complete in under 5ms       |
| **No Network Calls** | Cannot fetch external data       |
| **Deterministic**    | Same input must give same output |
| **Stateless**        | No access to previous executions |
| **Cart Data Only**   | Limited to provided input query  |

## Example Prompts

<AccordionGroup>
  <Accordion title="Tiered Spend Discount">
    ```
    Create a discount function with multiple spend tiers:
    - $50-$99: 5% off
    - $100-$199: 10% off
    - $200+: 15% off
    Apply to entire order. Show the tier achieved in the discount message.
    ```
  </Accordion>

  <Accordion title="Mix and Match">
    ```
    Create a discount where customers get 20% off when they buy 
    at least 3 items from the 'summer-collection'. Items can be 
    any combination. Show "Summer Mix & Match: 20% off" message.
    ```
  </Accordion>

  <Accordion title="First Order Discount">
    ```
    Give first-time customers (tagged 'first-order') 15% off 
    their entire order. Check if customer is tagged, apply 
    discount to order subtotal. Message: "Welcome! First order discount"
    ```
  </Accordion>

  <Accordion title="Loyalty Tier Pricing">
    ```
    Create tiered discounts based on customer tags:
    - 'loyalty-bronze': 5% off
    - 'loyalty-silver': 10% off
    - 'loyalty-gold': 15% off
    Apply to all products. Show tier name in message.
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Payment Customization" icon="credit-card" href="/guides/payment-customization">
    Customize payment methods
  </Card>

  <Card title="Shipping Customization" icon="truck" href="/guides/shipping-customization">
    Customize shipping rates
  </Card>

  <Card title="Functions Overview" icon="function" href="/essentials/functions">
    Learn about all function types
  </Card>

  <Card title="Debugging" icon="bug" href="/advanced/debugging">
    Debug and optimize functions
  </Card>
</CardGroup>
