spree_mydhl 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5eb4386b2de341e46d441e151c6f30066a2527c2b07057b1b136e79cacced47e
4
+ data.tar.gz: 78286b36ff2b4b9c88bae7a573a5a9bed6f00e7842cbdf464722647f0507470e
5
+ SHA512:
6
+ metadata.gz: 0ce2c255a23119803f03bee73e2de65316c8e11991cb55ec97e19e91521ce077189d377d5635073097c33d50b72ecb66749f66e1a30d6949359f6a38c77f4e9d
7
+ data.tar.gz: 4b4ac034a59c4bd52be12437f3add590dccf960220cd28e7bd2009ed5cc45a81cf31e0bc4fab4bd8a7339904d0418ca488f1970a92d516c887668f3addd698bc
data/LICENSE.md ADDED
@@ -0,0 +1,14 @@
1
+ # LICENSE
2
+
3
+ Copyright (c) 2026 [name of plugin creator]
4
+ All rights reserved.
5
+
6
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU Affero General Public License for more details.
12
+
13
+ You should have received a copy of the GNU Affero General Public License
14
+ along with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Spree MyDHL
2
+
3
+ A [Spree Commerce](https://spreecommerce.org) extension that adds MyDHL as a real-time shipping rate calculator. It connects to the [DHL MyDHL API](https://developer.dhl.com/api-reference/dhl-express-mydhl-api) during checkout to fetch live rates for your configured shipping methods.
4
+
5
+ ## Features
6
+
7
+ - Real-time rates from the MyDHL API
8
+ - Filters available shipping methods by Spree stock location
9
+ - Optional product code filter (e.g. lock a shipping method to *Express Worldwide* only)
10
+ - Automatic international/domestic detection for customs declarations
11
+ - Optional weight-based availability rules (min/max)
12
+ - Configurable rate caching to avoid redundant API calls
13
+ - Sandbox and production API support
14
+ - Admin dropdown selectors for stock location and DHL product code
15
+
16
+ ## Requirements
17
+
18
+ - Ruby >= 3.2
19
+ - Spree >= 5.3.3
20
+
21
+ ## Installation
22
+
23
+ 1. Add the gem to your Gemfile:
24
+
25
+ ```ruby
26
+ bundle add spree_mydhl
27
+ ```
28
+
29
+ 2. Run the install generator:
30
+
31
+ ```bash
32
+ bundle exec rails g spree_mydhl:install
33
+ ```
34
+
35
+ 3. Restart your server.
36
+
37
+ ## Configuration
38
+
39
+ ### DHL API Credentials
40
+
41
+ Sign in to the [DHL Developer Portal](https://developer.dhl.com) and create an application to obtain your API Key and API Secret.
42
+
43
+ ### Setting Up a Shipping Method
44
+
45
+ 1. In the Spree admin, go to **Configuration → Shipping Methods** and create a new shipping method.
46
+ 2. Select **DHL Express** as the calculator.
47
+ 3. Fill in the calculator preferences:
48
+
49
+ | Preference | Description |
50
+ |---|---|
51
+ | **API Key** | Your DHL MyDHL API key |
52
+ | **API Secret** | Your DHL MyDHL API secret |
53
+ | **Account Number** | Your DHL account number |
54
+ | **Stock Location** | The stock location shipments originate from |
55
+ | **Unit of Measurement** | `metric` (kg/cm) or `imperial` (lb/in) — must match your variant dimensions |
56
+ | **Currency** | Currency for quoted rates (defaults to the store's default currency) |
57
+ | **Sandbox** | Enable to use the DHL test environment |
58
+ | **Product Code** | Optional — restrict to a specific DHL service (see below) |
59
+ | **Customs Declarable** | Optional — override automatic international detection |
60
+ | **Minimum Weight** | Optional — hide this method below a package weight threshold |
61
+ | **Maximum Weight** | Optional — hide this method above a package weight threshold |
62
+ | **Markup Percentage** | Optional — percentage added on top of the DHL rate (e.g. `10` adds 10%) |
63
+ | **Handling Fee** | Optional — flat amount added after any percentage markup |
64
+ | **Cache TTL Minutes** | How long to cache rates (default: `10`) |
65
+
66
+ ### DHL Product Codes
67
+
68
+ By default the calculator returns the cheapest rate across all DHL products available for the route. Set a product code to lock the shipping method to a specific service level:
69
+
70
+ | Code | Service |
71
+ |---|---|
72
+ | `P` | Express Worldwide |
73
+ | `D` | Express Worldwide Doc |
74
+ | `K` | Express 9:00 |
75
+ | `W` | Express 10:30 |
76
+ | `T` | Express 12:00 |
77
+ | `Y` | Express 12:00 Doc |
78
+ | `H` | Economy Select |
79
+ | `N` | Domestic Express |
80
+
81
+ ### Variant Dimensions
82
+
83
+ The API requires package dimensions. These are derived from your Spree variant attributes:
84
+
85
+ - **Length** — largest `depth` across all items in the package
86
+ - **Width** — largest `width` across all items in the package
87
+ - **Height** — sum of `height × quantity` across all items
88
+
89
+ Dimensions fall back to `1.0` if all variants report zero. Make sure your variant dimensions are stored in units consistent with the **Unit of Measurement** preference — no automatic conversion is applied.
90
+
91
+ ### Customs Declarations
92
+
93
+ By default, `isCustomsDeclarable` is set to `true` whenever the origin and destination country codes differ. Use the **Customs Declarable** preference to override this (e.g. for shipments between territories that share a country code).
94
+
95
+
96
+ ```
97
+
98
+ ## Development
99
+
100
+ ```bash
101
+ bundle update
102
+ bundle exec rake
103
+ bundle exec rubocop
104
+ ```
105
+
106
+ ## Releasing
107
+
108
+ ```bash
109
+ bundle exec gem bump -p -t
110
+ bundle exec gem release
111
+ ```
112
+
113
+ ## License
114
+
115
+ [AGPL-3.0-or-later](LICENSE.md)
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/extension_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task :default do
10
+ if Dir['spec/dummy'].empty?
11
+ Rake::Task[:test_app].invoke
12
+ Dir.chdir('../../')
13
+ end
14
+ Rake::Task[:spec].invoke
15
+ end
16
+
17
+ desc 'Generates a dummy app for testing'
18
+ task :test_app do
19
+ ENV['LIB_NAME'] = 'spree_mydhl'
20
+ Rake::Task['extension:test_app'].execute(
21
+ install_storefront: true,
22
+ install_admin: true
23
+ )
24
+ end
@@ -0,0 +1,52 @@
1
+ module Spree
2
+ module Admin
3
+ module BaseHelperDecorator
4
+ def preference_fields(object, form, i18n_scope: '')
5
+ return super unless object.is_a?(Spree::Calculator::Shipping::DhlExpress)
6
+
7
+ fields = Spree::Calculator::Shipping::DhlExpress::PREFERENCE_ORDER.map do |key|
8
+ key == :hr ? tag.hr(style: 'margin-top: 17px; margin-bottom: 34px;') : preference_field(object, form, key, i18n_scope: i18n_scope)
9
+ end
10
+ safe_join(fields)
11
+ end
12
+
13
+ def preference_field(object, form, key, i18n_scope: '')
14
+ return super unless object.is_a?(Spree::Calculator::Shipping::DhlExpress)
15
+
16
+ case key
17
+ when :unit_of_measurement
18
+ options = Spree::Calculator::Shipping::DhlExpress::UNIT_OF_MEASUREMENT_OPTIONS.map do |opt|
19
+ [opt.capitalize, opt]
20
+ end
21
+
22
+ content_tag(:div, class: 'form-group') do
23
+ form.label("preferred_#{key}", Spree.t(key, scope: i18n_scope, default: key.to_s.humanize)) +
24
+ form.select("preferred_#{key}", options, {}, class: 'form-select form-control')
25
+ end
26
+
27
+ when :product_code
28
+ content_tag(:div, class: 'form-group') do
29
+ form.label("preferred_#{key}", Spree.t(:product_code)) +
30
+ form.select("preferred_#{key}", Spree::Calculator::Shipping::DhlExpress::PRODUCT_CODE_OPTIONS,
31
+ { include_blank: false },
32
+ class: 'form-select form-control')
33
+ end
34
+
35
+ when :stock_location_id
36
+ stock_locations = Spree::StockLocation.active.order(:name).pluck(:name, :id)
37
+
38
+ content_tag(:div, class: 'form-group') do
39
+ form.label("preferred_#{key}", Spree.t(:stock_location)) +
40
+ form.select("preferred_#{key}", stock_locations, { include_blank: Spree.t(:select_stock_location) },
41
+ class: 'form-select form-control')
42
+ end
43
+
44
+ else
45
+ super
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ Spree::Admin::BaseHelper.prepend(Spree::Admin::BaseHelperDecorator)
@@ -0,0 +1,239 @@
1
+ module Spree
2
+ module Calculator::Shipping
3
+ class DhlExpress < ShippingCalculator
4
+ PREFERENCE_ORDER = %i[
5
+ hr
6
+ api_key
7
+ api_secret
8
+ account_number
9
+ hr
10
+ stock_location_id
11
+ product_code
12
+ unit_of_measurement
13
+ currency
14
+ sandbox
15
+ customs_declarable
16
+ hr
17
+ minimum_weight
18
+ maximum_weight
19
+ hr
20
+ markup_percentage
21
+ handling_fee
22
+ hr
23
+ cache_ttl_minutes
24
+ ].freeze
25
+
26
+ UNIT_OF_MEASUREMENT_OPTIONS = %w[metric imperial].freeze
27
+
28
+ PRODUCT_CODE_OPTIONS = [
29
+ ['Any (cheapest)', nil],
30
+ ['P — Express Worldwide', 'P'],
31
+ ['D — Express Worldwide Doc', 'D'],
32
+ ['K — Express 9:00', 'K'],
33
+ ['W — Express 10:30', 'W'],
34
+ ['T — Express 12:00', 'T'],
35
+ ['Y — Express 12:00 Doc', 'Y'],
36
+ ['H — Economy Select', 'H'],
37
+ ['N — Domestic Express', 'N']
38
+ ].freeze
39
+
40
+ preference :api_key, :string
41
+ preference :api_secret, :password
42
+ preference :account_number, :string
43
+ preference :stock_location_id, :integer
44
+ preference :unit_of_measurement, :string, default: UNIT_OF_MEASUREMENT_OPTIONS.first
45
+ preference :currency, :string, default: -> { Spree::Store.default.default_currency }
46
+ preference :sandbox, :boolean, default: false
47
+ preference :product_code, :string, default: nil, nullable: true
48
+ preference :customs_declarable, :boolean, default: nil, nullable: true
49
+ preference :minimum_weight, :decimal, default: nil, nullable: true
50
+ preference :maximum_weight, :decimal, default: nil, nullable: true
51
+ preference :markup_percentage, :decimal, default: nil, nullable: true
52
+ preference :handling_fee, :decimal, default: nil, nullable: true
53
+ preference :cache_ttl_minutes, :integer, default: 10
54
+
55
+ def self.description
56
+ 'MyDHL Live Rates'
57
+ end
58
+
59
+ def available?(package)
60
+ if required_preferences_blank?
61
+ Rails.logger.debug('[SpreeMydhl] available? = false: one or more required preferences are blank')
62
+ return false
63
+ end
64
+
65
+ if preferred_minimum_weight.present? && preferred_maximum_weight.present? &&
66
+ preferred_minimum_weight.to_f > preferred_maximum_weight.to_f
67
+ Rails.logger.warn('[SpreeMydhl] minimum_weight exceeds maximum_weight — no package can qualify; check shipping method configuration')
68
+ return false
69
+ end
70
+
71
+ stock_location = package.stock_location
72
+ if stock_location.nil? || stock_location.id.to_i != preferred_stock_location_id.to_i
73
+ Rails.logger.debug('[SpreeMydhl] available? = false: package stock location does not match configured stock location')
74
+ return false
75
+ end
76
+
77
+ address = package.order.ship_address
78
+ if address.nil?
79
+ Rails.logger.debug('[SpreeMydhl] available? = false: ship_address is nil')
80
+ return false
81
+ end
82
+
83
+ if address.country&.iso.blank?
84
+ Rails.logger.debug('[SpreeMydhl] available? = false: ship_address country ISO is blank')
85
+ return false
86
+ end
87
+
88
+ weight = package_weight(package)
89
+
90
+ if preferred_minimum_weight.present? && weight < preferred_minimum_weight.to_f
91
+ Rails.logger.debug("[SpreeMydhl] available? = false: weight #{weight} below minimum #{preferred_minimum_weight}")
92
+ return false
93
+ end
94
+
95
+ if preferred_maximum_weight.present? && weight > preferred_maximum_weight.to_f
96
+ Rails.logger.debug("[SpreeMydhl] available? = false: weight #{weight} above maximum #{preferred_maximum_weight}")
97
+ return false
98
+ end
99
+
100
+ true
101
+ end
102
+
103
+ def compute_package(package)
104
+ return nil unless available?(package)
105
+
106
+ stock_location = package.stock_location
107
+ origin_country = stock_location.country_iso
108
+
109
+ if origin_country.blank?
110
+ Rails.logger.debug('[SpreeMydhl] compute_package -> nil: stock location has no country ISO')
111
+ return nil
112
+ end
113
+
114
+ origin_postal = stock_location.zipcode.to_s
115
+ origin_city = stock_location.city.to_s
116
+
117
+ destination = package.order.ship_address
118
+ dest_country = destination.country.iso
119
+ dest_postal = destination.zipcode.to_s
120
+ dest_city = destination.city.to_s
121
+
122
+ weight = package_weight(package)
123
+ dimensions = package_dimensions(package)
124
+ currency = effective_currency(package)
125
+ cache_key = build_cache_key(origin_country, origin_postal, dest_country, dest_postal, dest_city, weight, dimensions, currency)
126
+
127
+ rate = Rails.cache.fetch(cache_key, expires_in: preferred_cache_ttl_minutes.minutes, skip_nil: true) do
128
+ client = SpreeMydhl::DhlExpressClient.new(
129
+ api_key: preferred_api_key,
130
+ api_secret: preferred_api_secret,
131
+ account_number: preferred_account_number,
132
+ origin_country_code: origin_country,
133
+ origin_postal_code: origin_postal,
134
+ origin_city_name: origin_city,
135
+ destination_country_code: dest_country,
136
+ destination_postal_code: dest_postal,
137
+ destination_city_name: dest_city,
138
+ weight: weight,
139
+ length: dimensions[:length],
140
+ width: dimensions[:width],
141
+ height: dimensions[:height],
142
+ unit_of_measurement: preferred_unit_of_measurement,
143
+ currency: currency,
144
+ sandbox: preferred_sandbox,
145
+ product_code: preferred_product_code,
146
+ customs_declarable: preferred_customs_declarable
147
+ )
148
+ client.cheapest_rate
149
+ end
150
+
151
+ rate = apply_markup(rate)
152
+ Rails.logger.debug("[SpreeMydhl] compute_package -> #{rate.inspect} (#{dest_country} #{dest_postal})")
153
+ rate
154
+ rescue StandardError => e
155
+ Rails.logger.error("[SpreeMydhl] compute_package failed: #{e.class}: #{e.message}")
156
+ Rails.logger.debug { Array(e.backtrace).first(5).join("\n") }
157
+ nil
158
+ end
159
+
160
+ private
161
+
162
+ def required_preferences_blank?
163
+ [
164
+ preferred_api_key,
165
+ preferred_api_secret,
166
+ preferred_account_number,
167
+ preferred_stock_location_id
168
+ ].any?(&:blank?)
169
+ end
170
+
171
+ def package_weight(package)
172
+ @package_weights ||= {}
173
+ @package_weights[package.object_id] ||= begin
174
+ w = package.weight
175
+ w.positive? ? w.to_f : 0.1
176
+ end
177
+ end
178
+
179
+ # Computes package dimensions from variant attributes.
180
+ # Variant depth/width/height and package weight must be stored in units that
181
+ # match the configured unit_of_measurement preference (metric: cm/kg, imperial: in/lb).
182
+ # No automatic unit conversion is applied.
183
+ def package_dimensions(package)
184
+ max_length = 0.0
185
+ max_width = 0.0
186
+ total_height = 0.0
187
+
188
+ package.contents.each do |content|
189
+ variant = content.variant
190
+ quantity = [content.quantity.to_i, 1].max
191
+ max_length = [max_length, variant.depth.to_f].max
192
+ max_width = [max_width, variant.width.to_f].max
193
+ total_height += variant.height.to_f * quantity
194
+ end
195
+
196
+ {
197
+ length: max_length.positive? ? max_length : 1.0,
198
+ width: max_width.positive? ? max_width : 1.0,
199
+ height: total_height.positive? ? total_height : 1.0
200
+ }
201
+ end
202
+
203
+ def effective_currency(package)
204
+ preferred_currency.presence || package.order.currency || package.order.store&.default_currency
205
+ end
206
+
207
+ def apply_markup(rate)
208
+ return nil if rate.nil?
209
+
210
+ rate = rate * (1 + preferred_markup_percentage.to_f / 100.0) if preferred_markup_percentage.present?
211
+ rate = rate + preferred_handling_fee.to_f if preferred_handling_fee.present?
212
+ rate.round(2)
213
+ end
214
+
215
+ def build_cache_key(origin_country, origin_postal, dest_country, dest_postal, dest_city, weight, dimensions, currency)
216
+ [
217
+ 'spree_mydhl',
218
+ 'rates',
219
+ preferred_account_number,
220
+ preferred_stock_location_id,
221
+ preferred_unit_of_measurement,
222
+ preferred_product_code,
223
+ preferred_customs_declarable,
224
+ origin_country,
225
+ origin_postal,
226
+ dest_country,
227
+ dest_postal,
228
+ dest_city,
229
+ weight.round(3),
230
+ dimensions[:length].round(2),
231
+ dimensions[:width].round(2),
232
+ dimensions[:height].round(2),
233
+ currency,
234
+ Date.current.iso8601
235
+ ].join('/')
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,3 @@
1
+ Rails.application.config.after_initialize do
2
+ Spree.calculators.shipping_methods << Spree::Calculator::Shipping::DhlExpress
3
+ end
@@ -0,0 +1,15 @@
1
+ ---
2
+ en:
3
+ spree:
4
+ account_number: Account number
5
+ api_key: MyDHL API key
6
+ api_secret: MyDHL API secret
7
+ cache_ttl_minutes: Cache TTL (minutes)
8
+ customs_declarable: Customs declarable
9
+ handling_fee: Handline fee
10
+ markup_percentage: Markup (%)
11
+ maximum_weight: Maximum weight
12
+ minimum_weight: Minimum_weight
13
+ product_code: DHL Product Code
14
+ sandbox: Sandbox
15
+ select_stock_location: "— Select stock location —"
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Spree::Core::Engine.add_routes do
2
+ # Add your extension routes here
3
+ end
@@ -0,0 +1,4 @@
1
+ module SpreeMydhl
2
+ class Configuration < Spree::Preferences::Configuration
3
+ end
4
+ end
@@ -0,0 +1,133 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'base64'
5
+
6
+ module SpreeMydhl
7
+ class DhlExpressClient
8
+ class ApiError < StandardError; end
9
+
10
+ PRODUCTION_BASE_URL = 'https://express.api.dhl.com/mydhlapi'.freeze
11
+ SANDBOX_BASE_URL = 'https://express.api.dhl.com/mydhlapi/test'.freeze
12
+
13
+ def initialize(api_key:, api_secret:, account_number:, origin_country_code:,
14
+ origin_postal_code:, origin_city_name:, destination_country_code:,
15
+ destination_postal_code:, destination_city_name:, weight:,
16
+ length:, width:, height:, unit_of_measurement: 'metric',
17
+ currency: 'USD', sandbox: false, product_code: nil,
18
+ customs_declarable: nil)
19
+ @api_key = api_key
20
+ @api_secret = api_secret
21
+ @account_number = account_number
22
+ @origin_country_code = origin_country_code
23
+ @origin_postal_code = origin_postal_code
24
+ @origin_city_name = origin_city_name
25
+ @destination_country_code = destination_country_code
26
+ @destination_postal_code = destination_postal_code
27
+ @destination_city_name = destination_city_name
28
+ @weight = weight
29
+ @length = length
30
+ @width = width
31
+ @height = height
32
+ @unit_of_measurement = unit_of_measurement
33
+ @currency = currency
34
+ @sandbox = sandbox
35
+ @product_code = product_code
36
+ @customs_declarable = customs_declarable
37
+ end
38
+
39
+ def cheapest_rate
40
+ data = fetch_rates
41
+ return nil if data.nil?
42
+
43
+ products = data['products']
44
+ return nil if products.nil? || products.empty?
45
+
46
+ if @product_code.present?
47
+ products = products.select { |p| p['productCode'] == @product_code }
48
+ return nil if products.empty?
49
+ end
50
+
51
+ prices = products.filter_map do |product|
52
+ total_prices = product['totalPrice']
53
+ next unless total_prices.is_a?(Array)
54
+
55
+ billed = total_prices.find { |p| p['currencyType'] == 'BILLC' }
56
+ billed&.fetch('price', nil)&.to_f
57
+ end
58
+
59
+ prices.empty? ? nil : prices.min
60
+ end
61
+
62
+ private
63
+
64
+ def fetch_rates
65
+ uri = build_uri
66
+ request = build_request(uri)
67
+
68
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 5, read_timeout: 10) do |http|
69
+ http.request(request)
70
+ end
71
+
72
+ unless response.is_a?(Net::HTTPSuccess)
73
+ raise ApiError, "DHL API returned HTTP #{response.code}: #{response.body.to_s[0, 200]}"
74
+ end
75
+
76
+ JSON.parse(response.body)
77
+ rescue ApiError => e
78
+ Rails.logger.error("[SpreeMydhl] DHL API error: #{e.message}")
79
+ Rails.logger.debug { Array(e.backtrace).first(5).join("\n") }
80
+ nil
81
+ rescue StandardError => e
82
+ Rails.logger.error("[SpreeMydhl] DHL request failed: #{e.class}: #{e.message}")
83
+ Rails.logger.debug { Array(e.backtrace).first(5).join("\n") }
84
+ nil
85
+ end
86
+
87
+ def build_uri
88
+ base_url = @sandbox ? SANDBOX_BASE_URL : PRODUCTION_BASE_URL
89
+ uri = URI("#{base_url}/rates")
90
+ uri.query = URI.encode_www_form(query_params.reject { |_, v| v.to_s.strip.empty? })
91
+ uri
92
+ end
93
+
94
+ def build_request(uri)
95
+ request = Net::HTTP::Get.new(uri)
96
+ request['Authorization'] = "Basic #{Base64.strict_encode64("#{@api_key}:#{@api_secret}")}"
97
+ request['Accept'] = 'application/json'
98
+ request['User-Agent'] = "spree_mydhl/#{SpreeMydhl::VERSION}"
99
+ request
100
+ end
101
+
102
+ def query_params
103
+ {
104
+ accountNumber: @account_number,
105
+ originCountryCode: @origin_country_code,
106
+ originPostalCode: @origin_postal_code,
107
+ originCityName: @origin_city_name,
108
+ destinationCountryCode: @destination_country_code,
109
+ destinationPostalCode: @destination_postal_code,
110
+ destinationCityName: @destination_city_name,
111
+ weight: @weight.round(3),
112
+ length: @length.round(2),
113
+ width: @width.round(2),
114
+ height: @height.round(2),
115
+ plannedShippingDate: planned_shipping_date,
116
+ unitOfMeasurement: @unit_of_measurement,
117
+ isCustomsDeclarable: customs_declarable?,
118
+ nextBusinessDay: true,
119
+ requestedCurrencyCode: @currency
120
+ }
121
+ end
122
+
123
+ def customs_declarable?
124
+ return @customs_declarable unless @customs_declarable.nil?
125
+
126
+ @origin_country_code.upcase != @destination_country_code.upcase
127
+ end
128
+
129
+ def planned_shipping_date
130
+ Date.current.iso8601
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,23 @@
1
+ module SpreeMydhl
2
+ class Engine < Rails::Engine
3
+ require 'spree/core'
4
+ isolate_namespace Spree
5
+ engine_name 'spree_mydhl'
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec
9
+ end
10
+
11
+ initializer 'spree_mydhl.environment', before: :load_config_initializers do |_app|
12
+ SpreeMydhl::Config = SpreeMydhl::Configuration.new
13
+ end
14
+
15
+ def self.activate
16
+ Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
17
+ Rails.configuration.cache_classes ? require(c) : load(c)
18
+ end
19
+ end
20
+
21
+ config.to_prepare(&method(:activate).to_proc)
22
+ end
23
+ end
@@ -0,0 +1,2 @@
1
+ FactoryBot.define do
2
+ end
@@ -0,0 +1,7 @@
1
+ module SpreeMydhl
2
+ VERSION = '0.0.1'.freeze
3
+
4
+ def self.gem_version
5
+ Gem::Version.new(VERSION)
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'spree'
2
+ require 'spree_extension'
3
+ require 'spree_mydhl/engine'
4
+ require 'spree_mydhl/version'
5
+ require 'spree_mydhl/configuration'
6
+ require 'spree_mydhl/dhl_express_client'
7
+
8
+ module SpreeMydhl
9
+ mattr_accessor :queue
10
+
11
+ def self.queue
12
+ @@queue ||= Spree.queues.default
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spree_mydhl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Kennedy
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: spree
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 5.3.3
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 5.3.3
26
+ - !ruby/object:Gem::Dependency
27
+ name: spree_admin
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 5.3.3
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 5.3.3
40
+ - !ruby/object:Gem::Dependency
41
+ name: spree_storefront
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 5.3.3
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 5.3.3
54
+ - !ruby/object:Gem::Dependency
55
+ name: spree_extension
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ email: m.kennedy@me.com
69
+ executables: []
70
+ extensions: []
71
+ extra_rdoc_files: []
72
+ files:
73
+ - LICENSE.md
74
+ - README.md
75
+ - Rakefile
76
+ - app/helpers/spree/admin/base_helper_decorator.rb
77
+ - app/models/spree/calculator/shipping/dhl_express.rb
78
+ - config/initializers/spree.rb
79
+ - config/locales/en.yml
80
+ - config/routes.rb
81
+ - lib/spree_mydhl.rb
82
+ - lib/spree_mydhl/configuration.rb
83
+ - lib/spree_mydhl/dhl_express_client.rb
84
+ - lib/spree_mydhl/engine.rb
85
+ - lib/spree_mydhl/factories.rb
86
+ - lib/spree_mydhl/version.rb
87
+ homepage: https://github.com/MatthewKennedy/spree_mydhl
88
+ licenses:
89
+ - AGPL-3.0-or-later
90
+ metadata: {}
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '3.2'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements:
105
+ - none
106
+ rubygems_version: 4.0.3
107
+ specification_version: 4
108
+ summary: Spree Commerce MyDHL Extension providing live shipping rates at checkout
109
+ test_files: []