# EasyParcel live courier rates as a real Spree shipping cost. # # The storefront has always shown live EasyParcel quotes — 20 couriers for a # Melaka address, with logos and durations. What was missing is that the chosen # one never reached Spree, so the customer picked "City-Link RM6.79" and the # order was charged whatever the flat-rate method said. The quote screen looked # decorative because of that last missing step, and I removed it on that reading. # This restores it by making the selection the thing Spree actually charges. # # The amount is read from order.private_metadata['easyparcel_amount'], written # ONLY by the storefront's /api/shipping/select route, which re-fetches the live # quotes server-side and confirms the chosen service_id really costs that. The # browser can PATCH cart metadata directly, so an unverified amount here would # let a customer set their own shipping to one sen. # # No quote chosen => nil => no shipping rate => no fulfillment => checkout cannot # complete. That is deliberate: a Malaysian order must carry a real courier. # # The class is defined INSIDE to_prepare: at initializer load time Spree is not # autoloaded yet, and `require 'spree/shipping_calculator'` raises LoadError # under Zeitwerk — that took the container down once. # # Bind-mounted — the prebuilt image mounts no app code. See docker-compose.yml. Rails.application.config.to_prepare do unless defined?(Spree::Calculator::Shipping::EasyParcelQuote) Spree::Calculator::Shipping.const_set( :EasyParcelQuote, Class.new(Spree::ShippingCalculator) do preference :currency, :string, default: 'MYR' def self.description 'EasyParcel live courier quote' end def compute_package(package) order = package.respond_to?(:order) ? package.order : nil return nil if order.nil? meta = order.private_metadata || {} raw = meta['easyparcel_amount'] || meta[:easyparcel_amount] return nil if raw.blank? BigDecimal(raw.to_s) rescue ArgumentError, TypeError # A malformed amount must not become a free shipment. nil end end ) end # Registering only populates the admin's calculator dropdown; the method below # gets the class assigned directly, so a nil list here must not abort boot. klass = Spree::Calculator::Shipping::EasyParcelQuote list = Rails.application.config.spree.calculators&.shipping_methods list << klass if list.respond_to?(:include?) && !list.include?(klass) end