Ví dụ về tập lệnh phí vận chuyển

Tập lệnh vận chuyển tương tác với phí vận chuyển và có thể thay đổi tiêu đề, khả năng hiển thị, đơn hàng hiển thị và giá của mức phí. Những tập lệnh này chạy khi khách hàng truy cập vào trang tùy chọn vận chuyển trong quá trình thanh toán.

Tập lệnh vận chuyển giảm giá gói đăng ký chỉ áp dụng cho lần thanh toán đầu tiên của gói đăng ký. Các lần thanh toán sau sẽ không được giảm giá bằng tập lệnh.

Để sử dụng mẫu trên trang này, tạo tập lệnh với mẫu trống.

Các bước thực hiện:

  1. Trong trang quản trị Shopify, vào Ứng dụng > Script Editor.
  2. Nhấp vào Create script (Tạo tập lệnh).
  3. Nhấp vào Phí vận chuyển.
  4. Chọn Blank template (Mẫu trống), rồi nhấp vào Create script (Tạo tập lệnh).
  5. Trong mục Ruby source code (Mã nguồn Ruby), xóa dòng mã mặc định: Output.cart = Input.cart
  6. Sao chép tập lệnh từ trang này và dán vào mục Ruby source code (Mã nguồn Ruby).
  7. Chỉnh sửa mục Customizable Settings (Cài đặt tùy chỉnh) của tập lệnh để hoạt động tại cửa hàng.
  8. Kiểm tra tập lệnh. Để biết thêm thông tin, tham khảo Thử nghiệm và gỡ lỗi Đoạn mã Shopify.
  9. Sau khi kiểm tra:
    • nhấp vào Save draft (Lưu bản nháp) để lưu bản nháp chưa đăng của tập lệnh hoặc
    • nhấp vào Save and publish (Lưu và đăng) để tạo và đăng đoạn mã.

Thêm thông báo vào tên mức phí dành cho tỉnh và quốc gia cụ thể

Sử dụng tập lệnh này để thêm thông báo vào phí vận chuyển dựa trên tỉnh và quốc gia của địa chỉ giao hàng.

Ví dụ: Thêm thông báo vào phí vận chuyển tại British Columbia và Ontario (Canada) và Washington và New York (Hoa Kỳ) cho biết Due to COVID-19 disruptions, shipping might take longer than normal.

# ================================ Customizable Settings ================================
# ================================================================
# Add Message to Rate Names for Province or Country
#
# If the cart's shipping address province/country matches
# the entered settings, all shipping rates will have the
# entered message appended to their name.
#
# - 'country_code' is a 2-character abbreviation for the
# applicable country
# - 'province_code_match_type' determines whether we look for
# provinces that do, or don't, match the entered options, or
# all provinces. Can be:
# - ':include' to look for provinces that DO match
# - ':exclude' to look for provinces that DO NOT match
# - ':all' to look for all provinces
# - 'province_codes' is a list of 2-character abbreviations for
# the applicable provinces
# - 'message' is the message to append to rate names
# ================================================================
RATE_MESSAGE_FOR_PROVINCE_COUNTRY = [
  {
    country_code: "CA",
    province_code_match_type: :include,
    province_codes: ["BC", "ON"],
    message: "Due to COVID-19 disruptions, shipping might take longer than normal."
  },
  {
    country_code: "US",
    province_code_match_type: :include,
    province_codes: ["NY", "WA"],
    message: "Due to COVID-19 disruptions, shipping might take longer than normal."
  },
]

# ================================ Script Code (do not edit) ===============================
# ================================================================
# ProvinceSelector
#
# Finds whether the supplied province code matches the entered
# strings.
# ================================================================
class ProvinceSelector
  def initialize(match_type, provinces)
    @match_type = match_type
    @provinces = provinces.map { |province| province.upcase.strip }
  end

  def match?(province_code)
    if @match_type == :all
      true
    else
      (@match_type == :include) == @provinces.any? { |province| province_code.upcase.strip == province }
    end
  end
end

# ================================================================
# AddMessageToRateForProvinceCountryCampaign
#
# If the cart's shipping address country/province matches the
# entered settings, all shipping rates will have the entered
# message appended to their name
# ================================================================
class AddMessageToRateForProvinceCountryCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    return if cart.shipping_address.nil?

    address = cart.shipping_address

    @campaigns.each do |campaign|
      next unless address.country_code.upcase.strip == campaign[:country_code].upcase.strip

      province_selector = ProvinceSelector.new(campaign[:province_code_match_type],campaign[:province_codes])

      next unless province_selector.match?(address.province_code)

      shipping_rates.each do |shipping_rate|
        rate_name = shipping_rate.name + ' - ' + campaign[:message]
        shipping_rate.change_name(rate_name)
      end
    end
  end
end

CAMPAIGNS = [
  AddMessageToRateForProvinceCountryCampaign.new(RATE_MESSAGE_FOR_PROVINCE_COUNTRY),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Hiển thị phí cho mã zip, tỉnh và quốc gia cụ thể

Sử dụng tập lệnh này để chỉ hiển thị các mức phí vận chuyển cụ thể với khách hàng tại địa điểm cụ thể.

Ví dụ: Chỉ hiển thị một mức phí vận chuyển cụ thể với khách hàng ở vùng có mã ZIP 90210 của California. Mức phí này sẽ ẩn với tất cả các địa điểm khác.

# ================================ Customizable Settings ================================
# ================================================================
# Show Rate(s) for Zip/Province/Country
#
# If the cart's shipping address country/province/zip match the
# entered settings, the entered rate(s) are shown, and all other
# rates are hidden. Otherwise, the entered rate(s) are hidden.
#
# - 'country_code' is a 2-character abbreviation for the
# applicable country
# - 'province_code' is a list of 2-character abbreviations for
# the applicable province(s)
# - 'zip_code_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'zip_codes' is a list of strings to identify zip codes
# - 'rate_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - ':all' for all rates
# - 'rate_names' is a list of strings to identify rates
# - if using ':all' above, this can be set to 'nil'
# ================================================================
SHOW_RATES_FOR_ZIP_PROVINCE_COUNTRY = [
  {
    country_code: "US",
    province_code: "CA",
    zip_code_match_type: :exact,
    zip_codes: ["90210"],
    rate_match_type: :exact,
    rate_names: ["Shipping Rate"],
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ZipCodeSelector
#
# Finds whether the supplied zip code matches any of the entered
# strings.
# ================================================================
class ZipCodeSelector
  def initialize(match_type, zip_codes)
    @comparator = match_type == :exact ? '==' : 'include?'
    @zip_codes = zip_codes.map { |zip_code| zip_code.upcase.strip }
  end

  def match?(zip_code)
    @zip_codes.any? { |zip| zip_code.to_s.upcase.strip.send(@comparator, zip) }
  end
end

# ================================================================
# RateNameSelector
#
# Finds whether the supplied rate name matches any of the entered
# names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @match_type = match_type
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names&.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    if @match_type == :all
      true
    else
      @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
    end
  end
end

# ================================================================
# ShowRatesForZipProvinceCountryCampaign
#
# If the cart's shipping address zip/province/country match the
# entered settings, the entered rate(s) are shown, and all other
# rates are hidden. Otherwise, the entered rate(s) are hidden.
# ================================================================
class ShowRatesForZipProvinceCountryCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address

    @campaigns.each do |campaign|
      zip_code_selector = ZipCodeSelector.new(campaign[:zip_code_match_type], campaign[:zip_codes])
      rate_name_selector = RateNameSelector.new(campaign[:rate_match_type], campaign[:rate_names])

      if address.nil?
        full_match = false
      else
        country_match = address.country_code.upcase.strip == campaign[:country_code].upcase.strip
        province_match = address.province_code.upcase.strip == campaign[:province_code].upcase.strip
        zip_match = zip_code_selector.match?(address.zip)
        full_match = country_match && province_match && zip_match
      end

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate) != full_match
      end
    end
  end
end

CAMPAIGNS = [
  ShowRatesForZipProvinceCountryCampaign.new(SHOW_RATES_FOR_ZIP_PROVINCE_COUNTRY),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Ẩn phí cho mã zip, tỉnh và quốc gia cụ thể

Sử dụng tập lệnh này để ẩn các mức phí cụ thể với khách hàng tại địa điểm cụ thể.

Ví dụ: Ẩn một mức phí vận chuyển cụ thể với khách hàng ở vùng có mã ZIP 90210 tại California.

# ================================ Customizable Settings ================================
# ================================================================
# Hide Rate(s) for Zip/Province/Country
#
# If the cart's shipping address country/province/zip match the
# entered settings, the entered rate(s) are hidden.
#
# - 'country_code' is a 2-character abbreviation for the
# applicable country or region
# - 'province_code' is a list of 2-character abbreviations for
# the applicable provinces or states
# - 'zip_code_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'zip_codes' is a list of strings to identify zip codes
# - 'rate_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - ':all' for all rates
# - 'rate_names' is a list of strings to identify rates
# - if using ':all' above, this can be set to 'nil'
# ================================================================
HIDE_RATES_FOR_ZIP_PROVINCE_COUNTRY = [
  {
    country_code: "US",
    province_code: "CA",
    zip_code_match_type: :exact,
    zip_codes: ["90210"],
    rate_match_type: :exact,
    rate_names: ["Shipping Rate"],
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ZipCodeSelector
#
# Finds whether the supplied zip code matches any of the entered
# strings.
# ================================================================
class ZipCodeSelector
  def initialize(match_type, zip_codes)
    @comparator = match_type == :exact ? '==' : 'include?'
    @zip_codes = zip_codes.map { |zip_code| zip_code.upcase.strip }
  end

  def match?(zip_code)
    @zip_codes.any? { |zip| zip_code.to_s.upcase.strip.send(@comparator, zip) }
  end
end

# ================================================================
# RateNameSelector
#
# Finds whether the supplied rate name matches any of the entered
# names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @match_type = match_type
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names&.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    if @match_type == :all
      true
    else
      @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
    end
  end
end

# ================================================================
# HideRatesForZipProvinceCountryCampaign
#
# If the cart's shipping address zip/province/country match the
# entered settings, the entered rate(s) are hidden.
# ================================================================
class HideRatesForZipProvinceCountryCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address

    return if address.nil?

    @campaigns.each do |campaign|
      zip_code_selector = ZipCodeSelector.new(campaign[:zip_code_match_type], campaign[:zip_codes])

      country_match = address.country_code.upcase.strip == campaign[:country_code].upcase.strip
      province_match = address.province_code.upcase.strip == campaign[:province_code].upcase.strip
      zip_match = zip_code_selector.match?(address.zip)

      next unless country_match && province_match && zip_match

      rate_name_selector = RateNameSelector.new(campaign[:rate_match_type], campaign[:rate_names])

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate)
      end
    end
  end
end

CAMPAIGNS = [
  HideRatesForZipProvinceCountryCampaign.new(HIDE_RATES_FOR_ZIP_PROVINCE_COUNTRY),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Ẩn phí đối với sản phẩm cụ thể tại các quốc gia nhất định

Sử dụng tập lệnh này để ẩn phí với khách hàng tại quốc gia cụ thể khi khách mua một mặt hàng cụ thể.

Ví dụ: Ẩn một mức phí vận chuyển cụ thể với khách hàng tại Canada nếu khách mua áo thun.

# ================================ Customizable Settings ================================
# ================================================================
# Hide Rate(s) for Product/Country
#
# If the cart contains any matching items, and we have a matching
# country, the entered rate(s) are hidden.
#
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - 'product_selectors' is a list of tags or IDs to identify
# associated products
# - 'country_code_match_type' determines whether we look for
# countries that do, or don't, match the entered options, or
# all countries. Can be:
# - ':include' to look for countries that DO match
# - ':exclude' to look for countries that DO NOT match
# - ':all' to look for all countries
# - 'country_codes' is a list of country code abbreviations
# - ie. United States would be `US`
# - 'rate_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - ':all' for all rates
# - 'rate_names' is a list of strings to identify rates
# - if using ':all' above, this can be set to 'nil'
# ================================================================
HIDE_RATES_FOR_PRODUCT_AND_COUNTRY = [
  {
    product_selector_match_type: :include,
    product_selector_type: :product_id,
    product_selectors: [1234567890987, 1234567890986],
    country_code_match_type: :include,
    country_codes: ["CA"],
    rate_match_type: :exact,
    rate_names: ["Shipping Rate"],
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_type = selector_type
    @selectors = selectors
  end

  def match?(line_item)
    if self.respond_to?(@selector_type)
      self.send(@selector_type, line_item)
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  end

  def tag(line_item)
    product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@selectors & product_tags).send(@comparator)
  end

  def type(line_item)
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
  end

  def vendor(line_item)
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
  end

  def product_id(line_item)
    (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
  end

  def variant_id(line_item)
    (@match_type == :include) == @selectors.include?(line_item.variant.id)
  end

  def subscription(line_item)
    !line_item.selling_plan_id.nil?
  end
end

# ================================================================
# CountrySelector
#
# Finds whether the supplied country code matches the entered
# strings.
# ================================================================
class CountrySelector
  def initialize(match_type, countries)
    @match_type = match_type
    @countries = countries.map { |country| country.upcase.strip }
  end

  def match?(country_code)
    if @match_type == :all
      true
    else
      (@match_type == :include) == @countries.any? { |country| country_code.upcase.strip == country }
    end
  end
end

# ================================================================
# RateNameSelector
#
# Finds whether the supplied rate name matches any of the entered
# names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @match_type = match_type
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names&.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    if @match_type == :all
      true
    else
      @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
    end
  end
end

# ================================================================
# HideRatesForProductCountryCampaign
#
# If the cart contains any matching items, and we have a matching
# country, the entered rate(s) are hidden.
# ================================================================
class HideRatesForProductCountryCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address

    return if address.nil?

    @campaigns.each do |campaign|
      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors],
      )

      country_selector = CountrySelector.new(campaign[:country_code_match_type], campaign[:country_codes])
      product_match = cart.line_items.any? { |line_item| product_selector.match?(line_item) }
      country_match = country_selector.match?(address.country_code)

      next unless product_match && country_match

      rate_name_selector = RateNameSelector.new(
        campaign[:rate_match_type],
        campaign[:rate_names],
      )

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate)
      end
    end
  end
end

CAMPAIGNS = [
  HideRatesForProductCountryCampaign.new(HIDE_RATES_FOR_PRODUCT_AND_COUNTRY),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Ẩn phí khi giao hàng đến hộp thư bưu điện

Sử dụng tập lệnh này để ẩn mức phí cụ thể với khách hàng nếu địa chỉ giao hàng của khách là hộp thư bưu điện.

# ================================ Customizable Settings ================================
# ================================================================
# Hide Rate(s) for PO Box addresses
#
# If the shipping address contains any of the entered "PO Box"
# identifiers, the entered rate(s) are hidden.
#
# - 'po_box_triggers' is a list of possible strings for a PO
# Box address
# - 'rate_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - ':all' for all rates
# - 'rate_names' is a list of rates to show/hide if the above
# conditions are met
# ================================================================
HIDE_RATES_FOR_PO_BOX = [
  {
    po_box_triggers: [
      "po box", "post office", "p o box", "p.o.box", "p.o. box", "p.o box", "pobox",
      "post office box", "post box", "p. o. box", "po. box", "postal box",
    ],
    rate_match_type: :exact,
    rate_names: ["Shipping Rate", "Other Shipping Rate"],
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# AddressSelector
#
# Finds whether the supplied address contains any of the entered
# strings.
# ================================================================
class AddressSelector
  def initialize(triggers)
    @triggers = triggers.map { |trigger| trigger.downcase.strip }
  end

  def match?(address)
    address_fields = [address.address1, address.address2].map do |line|
      line.nil? ? "" : line.downcase
    end

    address_fields = address_fields.join(" ")
    @triggers.any? { |trigger| address_fields.include?(trigger) }
  end
end

# ================================================================
# RateNameSelector
#
# Finds whether the supplied rate name matches any of the entered
# names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @match_type = match_type
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    if @match_type == :all
      true
    else
      @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
    end
  end
end

# ================================================================
# HideRatesForPOBoxCampaign
#
# If the shipping address contains any of the entered "PO Box"
# identifiers, the entered rate(s) are hidden.
# ================================================================
class HideRatesForPOBoxCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address

    return if address.nil?

    @campaigns.each do |campaign|
      next unless AddressSelector.new(campaign[:po_box_triggers]).match?(address)

      rate_name_selector = RateNameSelector.new(
        campaign[:rate_match_type],
        campaign[:rate_names],
      )

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate)
      end
    end
  end
end

CAMPAIGNS = [
  HideRatesForPOBoxCampaign.new(HIDE_RATES_FOR_PO_BOX),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Hiển thị giá theo thẻ khách hàng

Sử dụng tập lệnh này để chỉ hiển thị mức phí cụ thể cho những khách hàng được gắn thẻ cụ thể.

Ví dụ: Chỉ hiển thị một mức phí vận chuyển cụ thể với khách hàng có thẻ VIP.

# ================================ Customizable Settings ================================
# ================================================================
# Show Rate(s) for Customer Tag
#
# If we have a matching customer, the entered rate(s) will be
# shown, and all others will be hidden. Otherwise, the entered
# rate(s) will be hidden.
#
# - 'customer_tag_match_type' determines whether we look for the
# customer to be tagged with any of the entered tags or not.
# Can be:
# - ':include' to check if the customer is tagged
# - ':exclude' to make sure the customer isn't tagged
# - 'customer_tags' is a list of customer tags to trigger the
# campaign
# - 'rate_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'rate_names' is a list of strings to identify rates
# ================================================================
SHOW_RATES_FOR_CUSTOMER_TAG = [
  {
    customer_tag_match_type: :include,
    customer_tags: ["customer_tag", "another_tag"],
    rate_match_type: :exact,
    rate_names: ["Shipping Rate", "Other Shipping Rate"],
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# CustomerTagSelector
#
# Finds whether the supplied customer has any of the entered tags.
# ================================================================
class CustomerTagSelector
  def initialize(match_type, tags)
    @comparator = match_type == :include ? 'any?' : 'none?'
    @tags = tags.map { |tag| tag.downcase.strip }
  end

  def match?(customer)
    customer_tags = customer.tags.map { |tag| tag.downcase.strip }
    (@tags & customer_tags).send(@comparator)
  end
end

# ================================================================
# RateNameSelector
#
# Finds whether the supplied rate name matches any of the entered
# names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
  end
end

# ================================================================
# ShowRateForCustomerTagCampaign
#
# If we have a matching customer, the entered rate(s) will be
# shown, and all others will be hidden. Otherwise, the entered
# rate(s) will be hidden.
# ================================================================
class ShowRateForCustomerTagCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    @campaigns.each do |campaign|
      customer_tag_selector = CustomerTagSelector.new(
        campaign[:customer_tag_match_type],
        campaign[:customer_tags]
      )

      customer_match = cart.customer.nil? ? false : customer_tag_selector.match?(cart.customer)

      rate_name_selector = RateNameSelector.new(
        campaign[:rate_match_type],
        campaign[:rate_names]
      )

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate) != customer_match
      end
    end
  end
end

CAMPAIGNS = [
  ShowRateForCustomerTagCampaign.new(SHOW_RATES_FOR_CUSTOMER_TAG),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Sắp xếp lại thứ tự mức phí

Sử dụng tập lệnh này để thay đổi thứ tự mặc định của phí vận chuyển cung cấp cho khách hàng.

# ================================ Customizable Settings ================================
# ================================================================
# The order in which you would like your rates to display
# ================================================================
DESIRED_RATE_ORDER = [
  "Shipping Rate 1", "Shipping Rate 2", "Shipping Rate 3",
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ReorderRatesCampaign
#
# Reorders rates into the entered order
# ================================================================
class ReorderRatesCampaign
  def initialize(desired_order)
    @desired_order = desired_order.map { |item| item.downcase.strip }
  end

  def run(cart, shipping_rates)
    shipping_rates.sort_by! { |rate| @desired_order.index(rate.name.downcase.strip) || Float::INFINITY }
  end
end

CAMPAIGNS = [
  ReorderRatesCampaign.new(DESIRED_RATE_ORDER),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Giảm giá mức phí bằng mã giảm giá

Sử dụng tập lệnh này để tặng ưu đãi giảm giá đối với một mức phí cụ thể nếu khách hàng sử dụng mã giảm giá cụ thể trong trang thanh toán.

Ví dụ: Khách hàng sử dụng mã giảm giá "DISCOUNT_15" sẽ được chiết khấu một mức cụ thể.

# ================================ Customizable Settings ================================
# ================================================================
# Discount Rate(s) by Discount Code(s)
#
# If one of the entered discount codes is used, the entered
# rate(s) are discounted by the entered amount.
#
# - 'discount_code_match_type' determines whether the below
# strings should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'discount_codes' is a list of strings to identify discount
# codes
# - 'rate_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'rate_names' is a list of strings to identify rates
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
DISCOUNTS_FOR_DISCOUNT_CODES = [
  {
    discount_code_match_type: :exact,
    discount_codes: ["TESTCODE1", "TESTCODE2"],
    rate_match_type: :exact,
    rate_names: ["Shipping Rate", "Other Shipping Rate"],
    discount_type: :percent,
    discount_amount: 100,
    discount_message: "Free Shipping with discount code"
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# DiscountCodeSelector
#
# Finds whether the supplied discount code matches any of the
# entered codes.
# ================================================================
class DiscountCodeSelector
  def initialize(match_type, discount_codes)
    @comparator = match_type == :exact ? '==' : 'include?'
    @discount_codes = discount_codes.map { |discount_code| discount_code.upcase.strip }
  end

  def match?(discount_code)
    @discount_codes.any? { |code| discount_code.code.upcase.send(@comparator, code) }
  end
end

# ================================================================
# RateNameSelector
#
# Finds whether the supplied rate name matches any of the entered
# names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
  end
end

# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied shipping rate.
# ================================================================
class DiscountApplicator
  def initialize(discount_type, discount_amount, discount_message)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_amount = if discount_type == :percent
      discount_amount * 0.01
    else
      Money.new(cents: 100) * discount_amount
    end
  end

  def apply(shipping_rate)
    rate_discount = if @discount_type == :percent
      shipping_rate.price * @discount_amount
    else
      @discount_amount
    end

    shipping_rate.apply_discount(rate_discount, message: @discount_message)
  end
end

# ================================================================
# DiscountRatesForDiscountCodeCampaign
#
# If one of the entered discount codes is used, the entered
# rate(s) are discounted by the entered amount.
# ================================================================
class DiscountRatesForDiscountCodeCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    return if cart.discount_code.nil?

    @campaigns.each do |campaign|
      discount_code_selector = DiscountCodeSelector.new(campaign[:discount_code_match_type], campaign[:discount_codes])

      next unless discount_code_selector.match?(cart.discount_code)

      rate_name_selector = RateNameSelector.new(campaign[:rate_match_type], campaign[:rate_names])
      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message],
      )

      shipping_rates.each do |shipping_rate|
        next unless rate_name_selector.match?(shipping_rate)
        discount_applicator.apply(shipping_rate)
      end
    end
  end
end

CAMPAIGNS = [
  DiscountRatesForDiscountCodeCampaign.new(DISCOUNTS_FOR_DISCOUNT_CODES),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Ưu đãi giảm giá mức phí theo thẻ khách hàng

Sử dụng tập lệnh này để chiết khấu mức phí cụ thể cho những khách hàng được gắn thẻ cụ thể.

Ví dụ: Tặng ưu đãi giảm giá 10% đối với một mức phí cụ thể nếu khách hàng có thẻ VIP.

# ================================ Customizable Settings ================================
# ================================================================
# Discount Rate(s) for Customer Tag(s)
#
# If we have a matching customer, the entered rate(s) are
# discounted by the entered amount.
#
# - 'customer_tag_match_type' determines whether we look for the customer
# to be tagged with any of the entered tags or not. Can be:
# - ':include' to check if the customer is tagged
# - ':exclude' to make sure the customer isn't tagged
# - 'customer_tags' is a list of customer tags to qualify for
# a discount
# - 'rate_match_type' determines whether the below strings
# should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'rate_names' is a list of strings to identify rates
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
DISCOUNTS_FOR_CUSTOMER_TAG = [
  {
    customer_tag_match_type: :include,
    customer_tags: ["customer_tag", "another_tag"],
    rate_match_type: :exact,
    rate_names: ["Shipping Rate", "Other Shipping Rate"],
    discount_type: :percent,
    discount_amount: 10,
    discount_message: "10% off shipping for tagged customers"
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# CustomerTagSelector
#
# Finds whether the supplied customer has any of the entered tags
# ================================================================
class CustomerTagSelector
  def initialize(match_type, tags)
    @comparator = match_type == :include ? 'any?' : 'none?'
    @tags = tags.map { |tag| tag.downcase.strip }
  end

  def match?(customer)
    customer_tags = customer.tags.map { |tag| tag.downcase.strip }
    (@tags & customer_tags).send(@comparator)
  end
end

# ================================================================
# RateNameSelector
#
# Finds whether the supplied rate name matches any of the entered
# names
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
  end
end

# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied shipping rate
# ================================================================
class DiscountApplicator
  def initialize(discount_type, discount_amount, discount_message)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_amount = if discount_type == :percent
      discount_amount * 0.01
    else
      Money.new(cents: 100) * discount_amount
    end
  end

  def apply(shipping_rate)
    rate_discount = if @discount_type == :percent
      shipping_rate.price * @discount_amount
    else
      @discount_amount
    end

    shipping_rate.apply_discount(rate_discount, message: @discount_message)
  end
end

# ================================================================
# DiscountRatesForCustomerTagCampaign
#
# If we have a matching customer, the entered rate(s) are
# discounted by the entered amount.
# ================================================================
class DiscountRatesForCustomerTagCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    return if cart.customer.nil?

    @campaigns.each do |campaign|
      customer_tag_selector = CustomerTagSelector.new(campaign[:customer_tag_match_type], campaign[:customer_tags])

      next unless customer_tag_selector.match?(cart.customer)

      rate_name_selector = RateNameSelector.new(campaign[:rate_match_type], campaign[:rate_names])
      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message],
      )

      shipping_rates.each do |shipping_rate|
        next unless rate_name_selector.match?(shipping_rate)
        discount_applicator.apply(shipping_rate)
      end
    end
  end
end

CAMPAIGNS = [
  DiscountRatesForCustomerTagCampaign.new(DISCOUNTS_FOR_CUSTOMER_TAG),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Chỉ hiển thị phí thực tính theo hãng vận chuyển

Sử dụng tập lệnh này để chỉ hiển thị phí thực tính theo hãng vận chuyển. Nếu không có phí thực tính theo hãng vận chuyển, mức phí do bạn tạo thủ công sẽ hiển thị.

# ================================================================
# Carrier Calculated Rate Fallback
#
# As long as carrier calculated rates are available, Shopify rates
# will be hidden.
# ================================================================
class CalculatedRateFallbackCampaign
  def run(shipping_rates)
    has_calculated_rates = shipping_rates.any?{ |shipping_rate| shipping_rate.source.downcase != 'shopify' }

    return unless has_calculated_rates

    shipping_rates.delete_if { |shipping_rate| shipping_rate.source.downcase == 'shopify' }
  end
end

CAMPAIGNS = [
  CalculatedRateFallbackCampaign.new(),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

Bạn đã sẵn sàng bán hàng với Shopify?

Dùng thử miễn phí