Shopify Development

Advanced Shopify Pricing: Implementing Dynamic Fees by State with Discount Functions

Mobile phone screen showing an Apple Pay checkout with a product and a state-specific environmental fee applied
Mobile phone screen showing an Apple Pay checkout with a product and a state-specific environmental fee applied

The Challenge: Dynamic, Location-Based Fees on Shopify

As your e-commerce business expands, so does the complexity of your pricing strategy. One common hurdle merchants face is implementing dynamic fees that vary based on a customer's geographical location, such as a state or province. This could be anything from regulatory charges, environmental recycling fees, or even specific delivery surcharges. While the concept seems straightforward, integrating these rules seamlessly into a Shopify store, especially when considering modern checkout experiences like Apple Pay or Google Pay, can present significant technical challenges.

Recently, a compelling discussion emerged in the Shopify Community, perfectly illustrating this dilemma. A merchant, JiangMintun, sought to implement an "Environmental Protection Recycling Fee" that needed to be charged differently across various US states. Their initial, clever solution hit a snag when confronted with accelerated checkout flows, a scenario many developers and store owners can relate to.

The Initial Approach: UI Extensions, Cart Attributes, and Their Accelerated Checkout Pitfalls

JiangMintun's initial strategy was well-conceived for standard checkout processes. The plan involved:

  1. UI Extension: Utilizing a UI Extension to capture the customer's shipping address as they entered it.
  2. Cart Attributes: Writing this address information into the cart attributes.
  3. Discount Function API: Employing a Discount Function API script to read these cart attributes, determine the delivery area, and subsequently apply the correct fee.

This method is intuitive and often effective for many custom logic implementations on Shopify. However, it quickly ran into trouble with accelerated checkout options like Apple Pay or Google Pay. The core issue? In these fast-paced checkout scenarios, the UI Extension might not always execute in time or correctly, leading to crucial cart attributes being missing or improperly populated. JiangMintun even attempted a fallback—using the billing address—only to discover that even the billing address could be null during an express checkout, leaving the fee calculation in limbo. This led to a less-than-ideal user experience, requiring a prompt for users to manually fill in shipping details before proceeding with express payment.

The Modern Solution: Leveraging Shopify Functions and Delivery Groups

The key to overcoming this challenge lies in embracing Shopify's more modern and robust development tools, specifically the Shopify Functions API. As highlighted by community expert v.marychenka, the solution involves directly accessing deliveryGroups within the Discount Function itself.

Understanding deliveryGroups and deliveryAddress.provinceCode

Shopify Functions, unlike some legacy APIs, provide direct access to the customer's shipping address information through the deliveryGroups object. Each deliveryGroup contains a deliveryAddress object, which in turn exposes the provinceCode (or state code for the US) directly. This means your Discount Function can read the customer's shipping state without relying on UI Extensions or cart attributes, which are prone to failure in accelerated checkout environments.

Crucially, this requires using the newer Shopify Functions API. If you're still working with legacy Product Discount or Order Discount APIs, you'll find that cart.deliveryGroups will always appear empty. This was the source of JiangMintun's observation that deliveryGroups returned an empty array in [cart.lines.discounts.generate.run] – a clear indicator that the function context or API version might not be aligned with the capabilities of modern Shopify Functions.

Implementing Robust State-Specific Logic

Here’s a conceptual look at how you might access this information within a Shopify Discount Function:

// Inside a Shopify Discount Function
import type { FunctionRunInput, FunctionRunResult } from "shopify/functions";
import { DiscountApplicationStrategy } from "shopify/functions";

export function run(input: FunctionRunInput): FunctionRunResult {
  const deliveryGroups = input.cart.deliveryGroups;

  if (!deliveryGroups || deliveryGroups.length === 0) {
    // Handle cases where delivery groups are not available (e.g., no address yet, or legacy API context)
    return { discountApplicationStrategy: DiscountApplicationStrategy.FIRST, discounts: [] };
  }

  const firstDeliveryGroup = deliveryGroups[0]; // Assuming a single delivery group for simplicity
  const provinceCode = firstDeliveryGroup.deliveryAddress?.provinceCode; // Safely access provinceCode

  if (provinceCode) {
    // Implement your state-specific fee logic here
    if (provinceCode === "CA") {
      // Apply a specific fee for California
      // Example: Find the product and add a line item discount or modify price
    } else if (provinceCode === "NY") {
      // Apply a specific fee for New York
      // ...
    }
    // Add more state-specific rules as needed
  }

  return { discountApplicationStrategy: DiscountApplicationStrategy.FIRST, discounts: [] };
}

Key Considerations:

  • Null Handling: Always account for deliveryAddress being potentially empty (e.g., deliveryAddress?.city or deliveryAddress?.provinceCode). Your function should gracefully handle scenarios where this data might not be immediately available.
  • Testing Accelerated Checkouts: Rigorously test your solution with Apple Pay, Google Pay, and other accelerated checkout methods. This is where the previous solution failed, and it's critical to verify the new approach works flawlessly.
  • Shopify Developer Community: If you encounter specific issues, the Shopify Developer Community is an invaluable resource. It's where developers working directly with these APIs share insights and solutions.

Why This Matters for Your Business

Implementing accurate, location-based fees is not just a technical exercise; it's vital for compliance, transparent pricing, and maintaining customer trust. Whether it's an environmental fee, a local tax, or a shipping surcharge, ensuring these are correctly applied at checkout, regardless of the payment method, is paramount. A robust solution prevents legal issues, avoids customer disputes, and streamlines your operational workflow.

For merchants looking to expand or optimize their online presence, choosing a platform like Shopify that offers advanced customization capabilities through its Functions API is crucial. If you're considering starting your own e-commerce journey or migrating an existing store, Shopify provides a powerful and flexible foundation for building and scaling your business.

Conclusion

While charging different fees for a specific product in different states on Shopify can seem complex, especially with accelerated checkouts, the solution lies in leveraging the power of Shopify Functions and directly accessing deliveryGroups. By moving beyond cart attributes and embracing the modern API, developers can build resilient, accurate, and seamless pricing logic that works across all checkout flows.

At Shopping Cart Mover, we specialize in helping businesses navigate these intricate development and integration challenges, ensuring your Shopify store operates at peak efficiency. If you're facing similar complex pricing scenarios or planning a migration that requires sophisticated custom development, don't hesitate to reach out to our experts.

Share:

Use cases

Explore use cases

Agencies, store owners, enterprise — find the migration path that fits.

Explore use cases