spree_api 5.4.0.rc2 → 5.4.0.rc4
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 +4 -4
- data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +2 -1
- data/app/controllers/concerns/spree/api/v3/error_handler.rb +5 -0
- data/app/controllers/spree/api/v3/base_controller.rb +3 -0
- data/app/controllers/spree/api/v3/store/carts/{coupon_codes_controller.rb → discount_codes_controller.rb} +7 -7
- data/app/controllers/spree/api/v3/store/carts/gift_cards_controller.rb +72 -0
- data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +2 -2
- data/app/controllers/spree/api/v3/store/policies_controller.rb +34 -0
- data/app/serializers/spree/api/v3/admin/price_history_serializer.rb +26 -0
- data/app/serializers/spree/api/v3/cart_serializer.rb +5 -3
- data/app/serializers/spree/api/v3/order_serializer.rb +5 -3
- data/app/serializers/spree/api/v3/policy_serializer.rb +22 -0
- data/app/serializers/spree/api/v3/price_history_serializer.rb +25 -0
- data/app/serializers/spree/api/v3/product_serializer.rb +8 -0
- data/app/serializers/spree/api/v3/variant_serializer.rb +8 -0
- data/app/services/spree/api/v3/filters_aggregator.rb +46 -11
- data/app/services/spree/webhooks/deliver_webhook.rb +25 -16
- data/app/subscribers/spree/webhook_event_subscriber.rb +18 -8
- data/config/locales/en.yml +9 -9
- data/config/routes.rb +9 -6
- data/lib/spree/api/dependencies.rb +3 -0
- data/lib/spree/api/openapi/schema_helper.rb +19 -0
- metadata +14 -10
- data/app/controllers/spree/api/v3/store/categories/products_controller.rb +0 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6509e525568a3b8ea64e7f0f798f4aa97269c1f050d8df3f8a0674fc53001935
|
|
4
|
+
data.tar.gz: 38bb74377c55de6b865eb28a1014fce6e5744714f44e8bf9484403e92d935044
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d748f15cd7c17a7c1bf1f9bc0bec2b22e4326c8a5088ab6989e5904ea2c315f0dad52035f6407a2995728e35ce2cf2b1adfb35f48a025d13007e658f4ee16ad1
|
|
7
|
+
data.tar.gz: c0c909f704cbdd2088b15fde911e7ceb4b919ea233c2145406bdac76e2e615046cd2b82a54c0992a4bdb518930123cd3e9c5b2a2fb4eb31d05bf98232a177cb7
|
|
@@ -26,7 +26,8 @@ module Spree
|
|
|
26
26
|
|
|
27
27
|
# Render the cart as JSON using the cart serializer.
|
|
28
28
|
def render_cart(status: :ok)
|
|
29
|
-
|
|
29
|
+
@cart = @cart.remove_out_of_stock_items!
|
|
30
|
+
render json: Spree.api.cart_serializer.new(@cart, params: serializer_params).to_h, status: status
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
# Render the order as JSON using the order serializer (for complete action).
|
|
@@ -48,6 +48,11 @@ module Spree
|
|
|
48
48
|
payment_processing_error: 'payment_processing_error',
|
|
49
49
|
gateway_error: 'gateway_error',
|
|
50
50
|
|
|
51
|
+
# Gift card errors
|
|
52
|
+
gift_card_not_found: 'gift_card_not_found',
|
|
53
|
+
gift_card_expired: 'gift_card_expired',
|
|
54
|
+
gift_card_already_redeemed: 'gift_card_already_redeemed',
|
|
55
|
+
|
|
51
56
|
# Digital download errors
|
|
52
57
|
attachment_missing: 'attachment_missing',
|
|
53
58
|
download_unauthorized: 'download_unauthorized',
|
|
@@ -2,6 +2,9 @@ module Spree
|
|
|
2
2
|
module Api
|
|
3
3
|
module V3
|
|
4
4
|
class BaseController < ActionController::API
|
|
5
|
+
# API v3 uses flat params — disable Rails' automatic parameter wrapping
|
|
6
|
+
wrap_parameters false
|
|
7
|
+
|
|
5
8
|
include ActiveStorage::SetCurrent
|
|
6
9
|
include CanCan::ControllerAdditions
|
|
7
10
|
include Spree::Core::ControllerHelpers::StrongParameters
|
|
@@ -3,14 +3,14 @@ module Spree
|
|
|
3
3
|
module V3
|
|
4
4
|
module Store
|
|
5
5
|
module Carts
|
|
6
|
-
class
|
|
6
|
+
class DiscountCodesController < Store::BaseController
|
|
7
7
|
include Spree::Api::V3::CartResolvable
|
|
8
8
|
include Spree::Api::V3::OrderLock
|
|
9
9
|
|
|
10
10
|
before_action :find_cart!
|
|
11
11
|
|
|
12
|
-
# POST /api/v3/store/carts/:cart_id/
|
|
13
|
-
# Apply a
|
|
12
|
+
# POST /api/v3/store/carts/:cart_id/discount_codes
|
|
13
|
+
# Apply a discount code to the cart
|
|
14
14
|
def create
|
|
15
15
|
with_order_lock do
|
|
16
16
|
@cart.coupon_code = permitted_params[:code]
|
|
@@ -25,9 +25,9 @@ module Spree
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
# DELETE /api/v3/store/carts/:cart_id/
|
|
29
|
-
# Remove a
|
|
30
|
-
# :id is the
|
|
28
|
+
# DELETE /api/v3/store/carts/:cart_id/discount_codes/:id
|
|
29
|
+
# Remove a discount code from the cart
|
|
30
|
+
# :id is the discount code string (e.g., SAVE10)
|
|
31
31
|
def destroy
|
|
32
32
|
with_order_lock do
|
|
33
33
|
coupon_handler.remove(params[:id])
|
|
@@ -43,7 +43,7 @@ module Spree
|
|
|
43
43
|
private
|
|
44
44
|
|
|
45
45
|
def coupon_handler
|
|
46
|
-
@coupon_handler ||= Spree.coupon_handler.new(@cart)
|
|
46
|
+
@coupon_handler ||= Spree.coupon_handler.new(@cart, enable_gift_cards: false)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def permitted_params
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Store
|
|
5
|
+
module Carts
|
|
6
|
+
class GiftCardsController < Store::BaseController
|
|
7
|
+
include Spree::Api::V3::CartResolvable
|
|
8
|
+
include Spree::Api::V3::OrderLock
|
|
9
|
+
|
|
10
|
+
before_action :find_cart!
|
|
11
|
+
|
|
12
|
+
# POST /api/v3/store/carts/:cart_id/gift_cards
|
|
13
|
+
def create
|
|
14
|
+
with_order_lock do
|
|
15
|
+
gift_card = find_gift_card!
|
|
16
|
+
return unless gift_card
|
|
17
|
+
|
|
18
|
+
result = @cart.apply_gift_card(gift_card)
|
|
19
|
+
|
|
20
|
+
if result.success?
|
|
21
|
+
render_cart(status: :created)
|
|
22
|
+
else
|
|
23
|
+
render_service_error(result.error)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# DELETE /api/v3/store/carts/:cart_id/gift_cards/:id
|
|
29
|
+
def destroy
|
|
30
|
+
with_order_lock do
|
|
31
|
+
result = @cart.remove_gift_card
|
|
32
|
+
|
|
33
|
+
if result.success?
|
|
34
|
+
render_cart
|
|
35
|
+
else
|
|
36
|
+
render_service_error(result.error)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def find_gift_card!
|
|
44
|
+
gift_card = @cart.store.gift_cards.find_by(code: permitted_params[:code]&.downcase)
|
|
45
|
+
|
|
46
|
+
if gift_card.nil?
|
|
47
|
+
render_error(code: ERROR_CODES[:gift_card_not_found], message: Spree.t(:gift_card_not_found), status: :not_found)
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if gift_card.expired?
|
|
52
|
+
render_error(code: ERROR_CODES[:gift_card_expired], message: Spree.t(:gift_card_expired), status: :unprocessable_content)
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if gift_card.redeemed?
|
|
57
|
+
render_error(code: ERROR_CODES[:gift_card_already_redeemed], message: Spree.t(:gift_card_already_redeemed), status: :unprocessable_content)
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
gift_card
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def permitted_params
|
|
65
|
+
params.permit(:code)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -13,7 +13,7 @@ module Spree
|
|
|
13
13
|
|
|
14
14
|
skip_before_action :authenticate_user
|
|
15
15
|
|
|
16
|
-
# POST /api/v3/store/
|
|
16
|
+
# POST /api/v3/store/password_resets
|
|
17
17
|
def create
|
|
18
18
|
redirect_url = params[:redirect_url]
|
|
19
19
|
|
|
@@ -41,7 +41,7 @@ module Spree
|
|
|
41
41
|
render json: { message: Spree.t(:password_reset_requested, scope: :api) }, status: :accepted
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
# PATCH /api/v3/store/
|
|
44
|
+
# PATCH /api/v3/store/password_resets/:id
|
|
45
45
|
def update
|
|
46
46
|
user = Spree.user_class.find_by_password_reset_token(params[:id])
|
|
47
47
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Store
|
|
5
|
+
class PoliciesController < Store::ResourceController
|
|
6
|
+
include Spree::Api::V3::HttpCaching
|
|
7
|
+
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def model_class
|
|
11
|
+
Spree::Policy
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serializer_class
|
|
15
|
+
Spree.api.policy_serializer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Accept slug (e.g., return-policy) or prefixed ID (e.g., pol_abc123)
|
|
19
|
+
def find_resource
|
|
20
|
+
if params[:id].to_s.start_with?('pol_')
|
|
21
|
+
scope.find_by_prefix_id!(params[:id])
|
|
22
|
+
else
|
|
23
|
+
scope.friendly.find(params[:id])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def scope
|
|
28
|
+
super.order(:name)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Api
|
|
5
|
+
module V3
|
|
6
|
+
module Admin
|
|
7
|
+
class PriceHistorySerializer < V3::PriceHistorySerializer
|
|
8
|
+
typelize variant_id: :string,
|
|
9
|
+
price_id: :string,
|
|
10
|
+
compare_at_amount: [:string, nullable: true],
|
|
11
|
+
created_at: :string
|
|
12
|
+
|
|
13
|
+
attribute :variant_id do |price_history|
|
|
14
|
+
price_history.variant.prefixed_id
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attribute :price_id do |price_history|
|
|
18
|
+
price_history.price.prefixed_id
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attributes :compare_at_amount, created_at: :iso8601
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -18,7 +18,9 @@ module Spree
|
|
|
18
18
|
gift_card_total: :string, display_gift_card_total: :string,
|
|
19
19
|
covered_by_store_credit: :boolean,
|
|
20
20
|
total: :string, display_total: :string,
|
|
21
|
+
amount_due: :string, display_amount_due: :string,
|
|
21
22
|
shipping_eq_billing_address: :boolean,
|
|
23
|
+
warnings: 'Array<{code: string, message: string, line_item_id?: string, variant_id?: string}>',
|
|
22
24
|
billing_address: { nullable: true }, shipping_address: { nullable: true },
|
|
23
25
|
gift_card: { nullable: true }
|
|
24
26
|
|
|
@@ -34,7 +36,9 @@ module Spree
|
|
|
34
36
|
:discount_total, :display_discount_total,
|
|
35
37
|
:tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
|
|
36
38
|
:additional_tax_total, :display_additional_tax_total, :total, :display_total,
|
|
37
|
-
:
|
|
39
|
+
:gift_card_total, :display_gift_card_total,
|
|
40
|
+
:amount_due, :display_amount_due,
|
|
41
|
+
:delivery_total, :display_delivery_total, :warnings,
|
|
38
42
|
created_at: :iso8601, updated_at: :iso8601
|
|
39
43
|
|
|
40
44
|
attribute :store_credit_total do |order|
|
|
@@ -45,8 +49,6 @@ module Spree
|
|
|
45
49
|
order.display_total_applied_store_credit.to_s
|
|
46
50
|
end
|
|
47
51
|
|
|
48
|
-
attributes :gift_card_total, :display_gift_card_total
|
|
49
|
-
|
|
50
52
|
attribute :covered_by_store_credit do |order|
|
|
51
53
|
order.covered_by_store_credit?
|
|
52
54
|
end
|
|
@@ -17,7 +17,9 @@ module Spree
|
|
|
17
17
|
store_credit_total: :string, display_store_credit_total: :string,
|
|
18
18
|
gift_card_total: :string, display_gift_card_total: :string,
|
|
19
19
|
covered_by_store_credit: :boolean,
|
|
20
|
-
total: :string, display_total: :string,
|
|
20
|
+
total: :string, display_total: :string,
|
|
21
|
+
amount_due: :string, display_amount_due: :string,
|
|
22
|
+
completed_at: [:string, nullable: true],
|
|
21
23
|
billing_address: { nullable: true }, shipping_address: { nullable: true },
|
|
22
24
|
gift_card: { nullable: true }
|
|
23
25
|
|
|
@@ -28,6 +30,8 @@ module Spree
|
|
|
28
30
|
:discount_total, :display_discount_total,
|
|
29
31
|
:tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
|
|
30
32
|
:additional_tax_total, :display_additional_tax_total, :total, :display_total,
|
|
33
|
+
:gift_card_total, :display_gift_card_total,
|
|
34
|
+
:amount_due, :display_amount_due,
|
|
31
35
|
:delivery_total, :display_delivery_total, :fulfillment_status, :payment_status,
|
|
32
36
|
completed_at: :iso8601, created_at: :iso8601, updated_at: :iso8601
|
|
33
37
|
|
|
@@ -39,8 +43,6 @@ module Spree
|
|
|
39
43
|
order.display_total_applied_store_credit.to_s
|
|
40
44
|
end
|
|
41
45
|
|
|
42
|
-
attributes :gift_card_total, :display_gift_card_total
|
|
43
|
-
|
|
44
46
|
attribute :covered_by_store_credit do |order|
|
|
45
47
|
order.covered_by_store_credit?
|
|
46
48
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
class PolicySerializer < BaseSerializer
|
|
5
|
+
typelize name: :string, slug: :string,
|
|
6
|
+
body: [:string, nullable: true], body_html: [:string, nullable: true]
|
|
7
|
+
|
|
8
|
+
attributes :name, :slug
|
|
9
|
+
|
|
10
|
+
attribute :body do |policy|
|
|
11
|
+
policy.body&.to_plain_text
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attribute :body_html do |policy|
|
|
15
|
+
policy.body&.body&.to_s.to_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attributes created_at: :iso8601, updated_at: :iso8601
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Api
|
|
5
|
+
module V3
|
|
6
|
+
class PriceHistorySerializer < BaseSerializer
|
|
7
|
+
typelize amount: :string,
|
|
8
|
+
amount_in_cents: :number,
|
|
9
|
+
display_amount: :string,
|
|
10
|
+
currency: :string,
|
|
11
|
+
recorded_at: :string
|
|
12
|
+
|
|
13
|
+
attributes :amount, :amount_in_cents, :currency
|
|
14
|
+
|
|
15
|
+
attribute :display_amount do |price_history|
|
|
16
|
+
price_history.display_amount
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attribute :recorded_at do |price_history|
|
|
20
|
+
price_history.recorded_at&.iso8601
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -111,6 +111,14 @@ module Spree
|
|
|
111
111
|
key: :metafields,
|
|
112
112
|
resource: Spree.api.metafield_serializer,
|
|
113
113
|
if: proc { expand?('metafields') }
|
|
114
|
+
|
|
115
|
+
typelize prior_price: ['PriceHistory', nullable: true]
|
|
116
|
+
|
|
117
|
+
attribute :prior_price,
|
|
118
|
+
if: proc { expand?('prior_price') } do |product|
|
|
119
|
+
record = price_in(product.default_variant)&.prior_price
|
|
120
|
+
Spree.api.price_history_serializer.new(record, params: params).to_h if record
|
|
121
|
+
end
|
|
114
122
|
end
|
|
115
123
|
end
|
|
116
124
|
end
|
|
@@ -85,6 +85,14 @@ module Spree
|
|
|
85
85
|
key: :metafields,
|
|
86
86
|
resource: Spree.api.metafield_serializer,
|
|
87
87
|
if: proc { expand?('metafields') }
|
|
88
|
+
|
|
89
|
+
typelize prior_price: ['PriceHistory', nullable: true]
|
|
90
|
+
|
|
91
|
+
attribute :prior_price,
|
|
92
|
+
if: proc { expand?('prior_price') } do |variant|
|
|
93
|
+
record = price_in(variant)&.prior_price
|
|
94
|
+
Spree.api.price_history_serializer.new(record, params: params).to_h if record
|
|
95
|
+
end
|
|
88
96
|
end
|
|
89
97
|
end
|
|
90
98
|
end
|
|
@@ -2,13 +2,17 @@ module Spree
|
|
|
2
2
|
module Api
|
|
3
3
|
module V3
|
|
4
4
|
class FiltersAggregator
|
|
5
|
-
# @param scope [ActiveRecord::Relation] Base product scope (
|
|
5
|
+
# @param scope [ActiveRecord::Relation] Base product scope (fully filtered, including option values)
|
|
6
6
|
# @param currency [String] Currency for price range
|
|
7
7
|
# @param category [Spree::Category, nil] Optional category for default_sort and category filtering context
|
|
8
|
-
|
|
8
|
+
# @param option_value_ids [Array<String>] Currently selected option value prefixed IDs (for disjunctive facet counts)
|
|
9
|
+
# @param scope_before_options [ActiveRecord::Relation] Scope before option value filters (for disjunctive counts)
|
|
10
|
+
def initialize(scope:, currency:, category: nil, option_value_ids: [], scope_before_options: nil)
|
|
9
11
|
@scope = scope
|
|
10
12
|
@currency = currency
|
|
11
13
|
@category = category
|
|
14
|
+
@option_value_ids = option_value_ids
|
|
15
|
+
@scope_before_options = scope_before_options || scope
|
|
12
16
|
end
|
|
13
17
|
|
|
14
18
|
def call
|
|
@@ -78,25 +82,26 @@ module Spree
|
|
|
78
82
|
|
|
79
83
|
def option_type_filters
|
|
80
84
|
Spree::OptionType.filterable.includes(:option_values).order(:position).filter_map do |option_type|
|
|
81
|
-
|
|
85
|
+
# Disjunctive: count against scope WITHOUT this option type's filter
|
|
86
|
+
count_scope = disjunctive_scope_for(option_type)
|
|
87
|
+
values = option_type.option_values.for_products(count_scope).distinct.order(:position)
|
|
82
88
|
next if values.empty?
|
|
83
89
|
|
|
90
|
+
count_ids = count_scope.reorder('').distinct.pluck(:id)
|
|
91
|
+
|
|
84
92
|
{
|
|
85
93
|
id: option_type.prefixed_id,
|
|
86
94
|
type: 'option',
|
|
87
95
|
name: option_type.name,
|
|
88
96
|
label: option_type.label,
|
|
89
|
-
options: values.map { |ov| option_value_data(
|
|
97
|
+
options: values.map { |ov| option_value_data(count_ids, ov) }
|
|
90
98
|
}
|
|
91
99
|
end
|
|
92
100
|
end
|
|
93
101
|
|
|
94
|
-
def option_value_data(
|
|
95
|
-
# Count products in scope that have this option value
|
|
96
|
-
# We use a subquery approach to avoid GROUP BY conflicts when scope includes joins (like in_category)
|
|
97
|
-
# Join directly to option_value_variants for efficiency (skips joining through option_values table)
|
|
102
|
+
def option_value_data(product_ids, option_value)
|
|
98
103
|
count = Spree::Product
|
|
99
|
-
.where(id:
|
|
104
|
+
.where(id: product_ids)
|
|
100
105
|
.joins(:option_value_variants)
|
|
101
106
|
.where(Spree::OptionValueVariant.table_name => { option_value_id: option_value.id })
|
|
102
107
|
.distinct
|
|
@@ -111,8 +116,38 @@ module Spree
|
|
|
111
116
|
}
|
|
112
117
|
end
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
# Returns the scope with all option type filters EXCEPT the given one applied.
|
|
120
|
+
# This gives disjunctive counts: selecting Blue still shows Red's true count.
|
|
121
|
+
def disjunctive_scope_for(option_type)
|
|
122
|
+
return @scope_before_options if grouped_selected_options.empty?
|
|
123
|
+
|
|
124
|
+
other_groups = grouped_selected_options.except(option_type.id)
|
|
125
|
+
|
|
126
|
+
# If this type has selections but no other types do, use scope before any option filters
|
|
127
|
+
return @scope_before_options if other_groups.empty?
|
|
128
|
+
|
|
129
|
+
# Rebuild: start from scope before options, apply only other option types
|
|
130
|
+
scope = @scope_before_options
|
|
131
|
+
other_groups.each_value do |ov_ids|
|
|
132
|
+
matching = Spree::Variant.where(deleted_at: nil)
|
|
133
|
+
.joins(:option_value_variants)
|
|
134
|
+
.where(Spree::OptionValueVariant.table_name => { option_value_id: ov_ids })
|
|
135
|
+
.select(:product_id)
|
|
136
|
+
scope = scope.where(id: matching)
|
|
137
|
+
end
|
|
138
|
+
scope
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Group selected option value IDs by option type (cached, single query)
|
|
142
|
+
def grouped_selected_options
|
|
143
|
+
@grouped_selected_options ||= begin
|
|
144
|
+
return {} if @option_value_ids.blank?
|
|
145
|
+
|
|
146
|
+
decoded = @option_value_ids.filter_map { |id| Spree::OptionValue.decode_prefixed_id(id) }
|
|
147
|
+
return {} if decoded.empty?
|
|
148
|
+
|
|
149
|
+
Spree::OptionValue.where(id: decoded).group_by(&:option_type_id).transform_values { |ovs| ovs.map(&:id) }
|
|
150
|
+
end
|
|
116
151
|
end
|
|
117
152
|
|
|
118
153
|
def category_filter
|
|
@@ -49,22 +49,31 @@ module Spree
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
51
|
def make_request
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
headers = {
|
|
53
|
+
'Content-Type' => 'application/json',
|
|
54
|
+
'User-Agent' => 'Spree-Webhooks/1.0',
|
|
55
|
+
'X-Spree-Webhook-Signature' => generate_signature,
|
|
56
|
+
'X-Spree-Webhook-Timestamp' => webhook_timestamp.to_s,
|
|
57
|
+
'X-Spree-Webhook-Event' => @delivery.event_name
|
|
58
|
+
}
|
|
59
|
+
body = @delivery.payload.to_json
|
|
60
|
+
http_options = { open_timeout: TIMEOUT, read_timeout: TIMEOUT, verify_mode: ssl_verify_mode }
|
|
61
|
+
|
|
62
|
+
# SSRF protection is disabled in development so webhooks can reach
|
|
63
|
+
# localhost / host.docker.internal (the storefront running on the host).
|
|
64
|
+
if Rails.env.development?
|
|
65
|
+
uri = URI.parse(@delivery.url)
|
|
66
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
67
|
+
http.use_ssl = uri.scheme == 'https'
|
|
68
|
+
http_options.each { |k, v| http.send(:"#{k}=", v) }
|
|
69
|
+
|
|
70
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
71
|
+
headers.each { |k, v| request[k] = v }
|
|
72
|
+
request.body = body
|
|
73
|
+
http.request(request)
|
|
74
|
+
else
|
|
75
|
+
SsrfFilter.post(@delivery.url, headers: headers, body: body, http_options: http_options)
|
|
76
|
+
end
|
|
68
77
|
end
|
|
69
78
|
|
|
70
79
|
def generate_signature
|
|
@@ -21,13 +21,15 @@ module Spree
|
|
|
21
21
|
return unless Spree::Api::Config.webhooks_enabled
|
|
22
22
|
return if event.store_id.blank?
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
endpoints = Spree::WebhookEndpoint
|
|
24
|
+
# Only load the columns we need for matching and delivery
|
|
25
|
+
endpoints = Spree::WebhookEndpoint
|
|
26
|
+
.enabled
|
|
27
|
+
.where(store_id: event.store_id)
|
|
28
|
+
.select(:id, :subscriptions)
|
|
26
29
|
|
|
27
|
-
return if endpoints.empty?
|
|
28
|
-
|
|
29
|
-
# Queue delivery for each endpoint
|
|
30
30
|
endpoints.each do |endpoint|
|
|
31
|
+
next unless endpoint.subscribed_to?(event.name)
|
|
32
|
+
|
|
31
33
|
queue_delivery(endpoint, event)
|
|
32
34
|
end
|
|
33
35
|
rescue StandardError => e
|
|
@@ -38,17 +40,25 @@ module Spree
|
|
|
38
40
|
private
|
|
39
41
|
|
|
40
42
|
def queue_delivery(endpoint, event)
|
|
41
|
-
# Build base payload (without delivery ID)
|
|
42
43
|
payload = build_payload(event)
|
|
43
44
|
|
|
44
|
-
#
|
|
45
|
+
# Deduplicate: skip if we already have a delivery for this event + endpoint
|
|
46
|
+
if event.id.present?
|
|
47
|
+
return if Spree::WebhookDelivery.exists?(
|
|
48
|
+
webhook_endpoint_id: endpoint.id,
|
|
49
|
+
event_id: event.id
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
delivery = endpoint.webhook_deliveries.create!(
|
|
46
54
|
event_name: event.name,
|
|
55
|
+
event_id: event.id,
|
|
47
56
|
payload: payload
|
|
48
57
|
)
|
|
49
58
|
|
|
50
|
-
# Queue the delivery job
|
|
51
59
|
Spree::WebhookDeliveryJob.perform_later(delivery.id)
|
|
60
|
+
rescue ActiveRecord::RecordNotUnique
|
|
61
|
+
# Race condition: another thread already created this delivery — safe to ignore
|
|
52
62
|
rescue StandardError => e
|
|
53
63
|
Rails.logger.error "[Spree Webhooks] Error queuing delivery for endpoint #{endpoint.id}: #{e.message}"
|
|
54
64
|
Rails.error.report(e)
|
data/config/locales/en.yml
CHANGED
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
en:
|
|
3
3
|
spree:
|
|
4
4
|
api:
|
|
5
|
+
current_password_invalid: Current password is invalid or missing
|
|
5
6
|
gateway_error: 'There was a problem with the payment gateway: %{text}'
|
|
6
7
|
invalid_api_key: Invalid API key (%{key}) specified.
|
|
7
8
|
invalid_resource: Invalid resource. Please fix errors and try again.
|
|
8
9
|
invalid_taxonomy_id: Invalid taxonomy id.
|
|
9
|
-
current_password_invalid: Current password is invalid or missing
|
|
10
|
-
password_reset_requested: If an account exists for that email, password reset instructions have been sent.
|
|
11
|
-
password_reset_token_invalid: Password reset token is invalid or has expired.
|
|
12
|
-
redirect_url_not_allowed: The redirect URL is not from an allowed origin for this store.
|
|
13
10
|
must_specify_api_key: You must specify an API key.
|
|
14
11
|
negative_quantity: quantity is negative
|
|
15
12
|
order:
|
|
16
13
|
could_not_transition: The order could not be transitioned. Please fix the errors and try again.
|
|
17
14
|
insufficient_quantity: An item in your cart has become unavailable.
|
|
18
15
|
invalid_shipping_method: Invalid shipping method specified.
|
|
16
|
+
password_reset_requested: If an account exists for that email, password reset instructions have been sent.
|
|
17
|
+
password_reset_token_invalid: Password reset token is invalid or has expired.
|
|
19
18
|
payment:
|
|
20
19
|
credit_over_limit: This payment can only be credited up to %{limit}. Please specify an amount less than or equal to this number.
|
|
21
20
|
update_forbidden: This payment cannot be updated because it is %{state}.
|
|
22
|
-
record_not_found:
|
|
21
|
+
record_not_found: "%{model} not found"
|
|
22
|
+
redirect_url_not_allowed: The redirect URL is not from an allowed origin for this store.
|
|
23
23
|
resource_not_found: The resource you were looking for could not be found.
|
|
24
24
|
shipment:
|
|
25
25
|
cannot_ready: Cannot ready shipment.
|
|
@@ -27,10 +27,6 @@ en:
|
|
|
27
27
|
shipment_transfer_success: Variants successfully transferred
|
|
28
28
|
stock_location_required: A stock_location_id parameter must be provided in order to retrieve stock movements.
|
|
29
29
|
unauthorized: You are not authorized to perform that action.
|
|
30
|
-
v3:
|
|
31
|
-
payments:
|
|
32
|
-
session_required: This payment method requires a payment session. Use the payment sessions endpoint instead.
|
|
33
|
-
method_unavailable: This payment method is not available for this order.
|
|
34
30
|
v2:
|
|
35
31
|
cart:
|
|
36
32
|
no_coupon_code: No coupon code provided and the Order doesn't have any coupon code promotions applied
|
|
@@ -47,4 +43,8 @@ en:
|
|
|
47
43
|
errors:
|
|
48
44
|
the_wishlist_could_not_be_destroyed: The wishlist could not be destroyed.
|
|
49
45
|
wrong_quantity: Quantity has to be greater than 0
|
|
46
|
+
v3:
|
|
47
|
+
payments:
|
|
48
|
+
method_unavailable: This payment method is not available for this order.
|
|
49
|
+
session_required: This payment method requires a payment session. Use the payment sessions endpoint instead.
|
|
50
50
|
wrong_shipment_target: target shipment is the same as original shipment
|
data/config/routes.rb
CHANGED
|
@@ -27,9 +27,7 @@ Spree::Core::Engine.add_routes do
|
|
|
27
27
|
get :filters, to: 'products/filters#index'
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
|
-
resources :categories, only: [:index, :show], id: /.+/
|
|
31
|
-
resources :products, only: [:index], controller: 'categories/products'
|
|
32
|
-
end
|
|
30
|
+
resources :categories, only: [:index, :show], id: /.+/
|
|
33
31
|
|
|
34
32
|
# Carts
|
|
35
33
|
resources :carts, only: [:index, :show, :create, :update, :destroy] do
|
|
@@ -38,7 +36,8 @@ Spree::Core::Engine.add_routes do
|
|
|
38
36
|
post :complete
|
|
39
37
|
end
|
|
40
38
|
resources :items, only: [:create, :update, :destroy], controller: 'carts/items'
|
|
41
|
-
resources :
|
|
39
|
+
resources :discount_codes, only: [:create, :destroy], controller: 'carts/discount_codes'
|
|
40
|
+
resources :gift_cards, only: [:create, :destroy], controller: 'carts/gift_cards'
|
|
42
41
|
resources :fulfillments, only: [:update], controller: 'carts/fulfillments'
|
|
43
42
|
resources :payments, only: [:create], controller: 'carts/payments'
|
|
44
43
|
resources :payment_sessions, only: [:create, :show, :update], controller: 'carts/payment_sessions' do
|
|
@@ -52,6 +51,12 @@ Spree::Core::Engine.add_routes do
|
|
|
52
51
|
# Orders (single order lookup, guest-accessible via order token)
|
|
53
52
|
resources :orders, only: [:show]
|
|
54
53
|
|
|
54
|
+
# Policies (return policy, privacy policy, terms of service, etc.)
|
|
55
|
+
resources :policies, only: [:index, :show]
|
|
56
|
+
|
|
57
|
+
# Password Resets (top-level, no auth required)
|
|
58
|
+
resources :password_resets, only: [:create, :update], controller: 'customer/password_resets'
|
|
59
|
+
|
|
55
60
|
# Customers
|
|
56
61
|
resources :customers, only: [:create]
|
|
57
62
|
|
|
@@ -60,8 +65,6 @@ Spree::Core::Engine.add_routes do
|
|
|
60
65
|
get '/', action: :show, controller: '/spree/api/v3/store/customers'
|
|
61
66
|
patch '/', action: :update, controller: '/spree/api/v3/store/customers'
|
|
62
67
|
|
|
63
|
-
resources :password_resets, only: [:create, :update]
|
|
64
|
-
|
|
65
68
|
resources :orders, only: [:index, :show]
|
|
66
69
|
resources :addresses, only: [:index, :show, :create, :update, :destroy]
|
|
67
70
|
resources :credit_cards, only: [:index, :show, :destroy]
|
|
@@ -94,6 +94,7 @@ module Spree
|
|
|
94
94
|
# v3 serializers (API v3)
|
|
95
95
|
credit_card_serializer: 'Spree::Api::V3::CreditCardSerializer',
|
|
96
96
|
price_serializer: 'Spree::Api::V3::PriceSerializer',
|
|
97
|
+
price_history_serializer: 'Spree::Api::V3::PriceHistorySerializer',
|
|
97
98
|
product_serializer: 'Spree::Api::V3::ProductSerializer',
|
|
98
99
|
variant_serializer: 'Spree::Api::V3::VariantSerializer',
|
|
99
100
|
media_serializer: 'Spree::Api::V3::MediaSerializer',
|
|
@@ -129,6 +130,7 @@ module Spree
|
|
|
129
130
|
gift_card_serializer: 'Spree::Api::V3::GiftCardSerializer',
|
|
130
131
|
currency_serializer: 'Spree::Api::V3::CurrencySerializer',
|
|
131
132
|
locale_serializer: 'Spree::Api::V3::LocaleSerializer',
|
|
133
|
+
policy_serializer: 'Spree::Api::V3::PolicySerializer',
|
|
132
134
|
metafield_serializer: 'Spree::Api::V3::MetafieldSerializer',
|
|
133
135
|
shipping_category_serializer: 'Spree::Api::V3::ShippingCategorySerializer',
|
|
134
136
|
tax_category_serializer: 'Spree::Api::V3::TaxCategorySerializer',
|
|
@@ -160,6 +162,7 @@ module Spree
|
|
|
160
162
|
admin_product_serializer: 'Spree::Api::V3::Admin::ProductSerializer',
|
|
161
163
|
admin_variant_serializer: 'Spree::Api::V3::Admin::VariantSerializer',
|
|
162
164
|
admin_price_serializer: 'Spree::Api::V3::Admin::PriceSerializer',
|
|
165
|
+
admin_price_history_serializer: 'Spree::Api::V3::Admin::PriceHistorySerializer',
|
|
163
166
|
admin_metafield_serializer: 'Spree::Api::V3::Admin::MetafieldSerializer',
|
|
164
167
|
admin_category_serializer: 'Spree::Api::V3::Admin::CategorySerializer',
|
|
165
168
|
admin_line_item_serializer: 'Spree::Api::V3::Admin::LineItemSerializer',
|
|
@@ -95,6 +95,17 @@ module Spree
|
|
|
95
95
|
},
|
|
96
96
|
required: %w[step field message]
|
|
97
97
|
},
|
|
98
|
+
CartWarning: {
|
|
99
|
+
type: :object,
|
|
100
|
+
description: 'A warning about a cart issue (e.g., item removed due to stock change)',
|
|
101
|
+
properties: {
|
|
102
|
+
code: { type: :string, description: 'Machine-readable warning code', example: 'line_item_removed' },
|
|
103
|
+
message: { type: :string, description: 'Human-readable warning message', example: 'Blue T-Shirt was removed because it was sold out' },
|
|
104
|
+
line_item_id: { type: :string, nullable: true, description: 'Prefixed line item ID (when applicable)', example: 'li_abc123' },
|
|
105
|
+
variant_id: { type: :string, nullable: true, description: 'Prefixed variant ID (when applicable)', example: 'variant_abc123' }
|
|
106
|
+
},
|
|
107
|
+
required: %w[code message]
|
|
108
|
+
},
|
|
98
109
|
FulfillmentManifestItem: {
|
|
99
110
|
type: :object,
|
|
100
111
|
description: 'An item within a fulfillment — which line item and how many units are in this fulfillment',
|
|
@@ -152,6 +163,14 @@ module Spree
|
|
|
152
163
|
items: { '$ref' => '#/components/schemas/CheckoutRequirement' }
|
|
153
164
|
}
|
|
154
165
|
end
|
|
166
|
+
|
|
167
|
+
warn_key = props.key?('warnings') ? 'warnings' : :warnings
|
|
168
|
+
if props[warn_key]
|
|
169
|
+
props[warn_key] = {
|
|
170
|
+
type: :array,
|
|
171
|
+
items: { '$ref' => '#/components/schemas/CartWarning' }
|
|
172
|
+
}
|
|
173
|
+
end
|
|
155
174
|
end
|
|
156
175
|
|
|
157
176
|
# Typelizer cannot represent Array<{...}> inline object types in OpenAPI,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.4.0.
|
|
4
|
+
version: 5.4.0.rc4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vendo Connect Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rswag-specs
|
|
@@ -72,28 +72,28 @@ dependencies:
|
|
|
72
72
|
requirements:
|
|
73
73
|
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: 0.
|
|
75
|
+
version: 0.11.0
|
|
76
76
|
type: :runtime
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 0.
|
|
82
|
+
version: 0.11.0
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: spree_core
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
87
|
- - '='
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: 5.4.0.
|
|
89
|
+
version: 5.4.0.rc4
|
|
90
90
|
type: :runtime
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - '='
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: 5.4.0.
|
|
96
|
+
version: 5.4.0.rc4
|
|
97
97
|
description: Spree's API
|
|
98
98
|
email:
|
|
99
99
|
- hello@spreecommerce.org
|
|
@@ -121,14 +121,14 @@ files:
|
|
|
121
121
|
- app/controllers/spree/api/v3/resource_controller.rb
|
|
122
122
|
- app/controllers/spree/api/v3/store/auth_controller.rb
|
|
123
123
|
- app/controllers/spree/api/v3/store/base_controller.rb
|
|
124
|
-
- app/controllers/spree/api/v3/store/carts/
|
|
124
|
+
- app/controllers/spree/api/v3/store/carts/discount_codes_controller.rb
|
|
125
125
|
- app/controllers/spree/api/v3/store/carts/fulfillments_controller.rb
|
|
126
|
+
- app/controllers/spree/api/v3/store/carts/gift_cards_controller.rb
|
|
126
127
|
- app/controllers/spree/api/v3/store/carts/items_controller.rb
|
|
127
128
|
- app/controllers/spree/api/v3/store/carts/payment_sessions_controller.rb
|
|
128
129
|
- app/controllers/spree/api/v3/store/carts/payments_controller.rb
|
|
129
130
|
- app/controllers/spree/api/v3/store/carts/store_credits_controller.rb
|
|
130
131
|
- app/controllers/spree/api/v3/store/carts_controller.rb
|
|
131
|
-
- app/controllers/spree/api/v3/store/categories/products_controller.rb
|
|
132
132
|
- app/controllers/spree/api/v3/store/categories_controller.rb
|
|
133
133
|
- app/controllers/spree/api/v3/store/countries_controller.rb
|
|
134
134
|
- app/controllers/spree/api/v3/store/currencies_controller.rb
|
|
@@ -145,6 +145,7 @@ files:
|
|
|
145
145
|
- app/controllers/spree/api/v3/store/markets/countries_controller.rb
|
|
146
146
|
- app/controllers/spree/api/v3/store/markets_controller.rb
|
|
147
147
|
- app/controllers/spree/api/v3/store/orders_controller.rb
|
|
148
|
+
- app/controllers/spree/api/v3/store/policies_controller.rb
|
|
148
149
|
- app/controllers/spree/api/v3/store/products/filters_controller.rb
|
|
149
150
|
- app/controllers/spree/api/v3/store/products_controller.rb
|
|
150
151
|
- app/controllers/spree/api/v3/store/resource_controller.rb
|
|
@@ -177,6 +178,7 @@ files:
|
|
|
177
178
|
- app/serializers/spree/api/v3/admin/payment_method_serializer.rb
|
|
178
179
|
- app/serializers/spree/api/v3/admin/payment_serializer.rb
|
|
179
180
|
- app/serializers/spree/api/v3/admin/payment_source_serializer.rb
|
|
181
|
+
- app/serializers/spree/api/v3/admin/price_history_serializer.rb
|
|
180
182
|
- app/serializers/spree/api/v3/admin/price_serializer.rb
|
|
181
183
|
- app/serializers/spree/api/v3/admin/product_serializer.rb
|
|
182
184
|
- app/serializers/spree/api/v3/admin/refund_serializer.rb
|
|
@@ -223,6 +225,8 @@ files:
|
|
|
223
225
|
- app/serializers/spree/api/v3/payment_session_serializer.rb
|
|
224
226
|
- app/serializers/spree/api/v3/payment_setup_session_serializer.rb
|
|
225
227
|
- app/serializers/spree/api/v3/payment_source_serializer.rb
|
|
228
|
+
- app/serializers/spree/api/v3/policy_serializer.rb
|
|
229
|
+
- app/serializers/spree/api/v3/price_history_serializer.rb
|
|
226
230
|
- app/serializers/spree/api/v3/price_serializer.rb
|
|
227
231
|
- app/serializers/spree/api/v3/product_serializer.rb
|
|
228
232
|
- app/serializers/spree/api/v3/promotion_serializer.rb
|
|
@@ -269,9 +273,9 @@ licenses:
|
|
|
269
273
|
- BSD-3-Clause
|
|
270
274
|
metadata:
|
|
271
275
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
272
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.
|
|
276
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.rc4
|
|
273
277
|
documentation_uri: https://docs.spreecommerce.org/
|
|
274
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.4.0.
|
|
278
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.4.0.rc4
|
|
275
279
|
post_install_message:
|
|
276
280
|
rdoc_options: []
|
|
277
281
|
require_paths:
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
module Spree
|
|
2
|
-
module Api
|
|
3
|
-
module V3
|
|
4
|
-
module Store
|
|
5
|
-
module Categories
|
|
6
|
-
class ProductsController < Store::ProductsController
|
|
7
|
-
before_action :set_category
|
|
8
|
-
|
|
9
|
-
protected
|
|
10
|
-
|
|
11
|
-
def set_category
|
|
12
|
-
@category = find_category
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def scope
|
|
16
|
-
super.in_category(@category)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def find_category
|
|
22
|
-
id = params[:category_id]
|
|
23
|
-
category_scope = Spree::Category.for_store(current_store).accessible_by(current_ability, :show)
|
|
24
|
-
category_scope = category_scope.i18n if Spree::Category.include?(Spree::TranslatableResource)
|
|
25
|
-
|
|
26
|
-
if id.to_s.start_with?('ctg_')
|
|
27
|
-
category_scope.find_by_prefix_id!(id)
|
|
28
|
-
else
|
|
29
|
-
find_with_fallback_default_locale { category_scope.i18n.find_by!(permalink: id) }
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|