spree_api 5.4.0.rc1 → 5.4.0.rc3

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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +2 -1
  3. data/app/controllers/concerns/spree/api/v3/error_handler.rb +5 -0
  4. data/app/controllers/spree/api/v3/base_controller.rb +3 -0
  5. data/app/controllers/spree/api/v3/store/carts/{coupon_codes_controller.rb → discount_codes_controller.rb} +7 -7
  6. data/app/controllers/spree/api/v3/store/carts/gift_cards_controller.rb +72 -0
  7. data/app/controllers/spree/api/v3/store/carts/payments_controller.rb +7 -28
  8. data/app/controllers/spree/api/v3/store/customer/addresses_controller.rb +5 -22
  9. data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +2 -2
  10. data/app/controllers/spree/api/v3/store/customer/store_credits_controller.rb +39 -0
  11. data/app/controllers/spree/api/v3/store/policies_controller.rb +34 -0
  12. data/app/serializers/spree/api/v3/address_serializer.rb +2 -2
  13. data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +17 -0
  14. data/app/serializers/spree/api/v3/admin/order_serializer.rb +1 -0
  15. data/app/serializers/spree/api/v3/admin/price_history_serializer.rb +26 -0
  16. data/app/serializers/spree/api/v3/cart_serializer.rb +23 -2
  17. data/app/serializers/spree/api/v3/customer_serializer.rb +13 -0
  18. data/app/serializers/spree/api/v3/order_serializer.rb +23 -2
  19. data/app/serializers/spree/api/v3/policy_serializer.rb +22 -0
  20. data/app/serializers/spree/api/v3/price_history_serializer.rb +25 -0
  21. data/app/serializers/spree/api/v3/product_serializer.rb +20 -2
  22. data/app/serializers/spree/api/v3/variant_serializer.rb +8 -0
  23. data/config/locales/en.yml +9 -9
  24. data/config/routes.rb +17 -15
  25. data/lib/spree/api/dependencies.rb +4 -0
  26. data/lib/spree/api/openapi/schema_helper.rb +19 -0
  27. metadata +14 -8
  28. data/app/controllers/spree/api/v3/store/carts/payment_methods_controller.rb +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4378e0d0d201591ada81caa1958a92ca6aa55d8e2ff79d8b90b58016ff1b834b
4
- data.tar.gz: 32fddc14b53ae38f7c80192be53a456e84b4862f58ddc3492a499ccbf2e67d53
3
+ metadata.gz: 314545a7037d2f446a6d764b8d130a6ba14c89cfdb82c380132595f74aa62435
4
+ data.tar.gz: 5976dd28153ffc543f658ca56fab3a2d97cefb162ec25481e192deb599743939
5
5
  SHA512:
6
- metadata.gz: 8f597f0fcb3d074cb90593a0feb619220642e13dbeefc994839b208f7639eabd0273e5b4a8223e8c4aa6ebbe9bd05add4559a9c6aac0de50cd837ee56060dcec
7
- data.tar.gz: 03e32f252a79ec570e5d91fc5ed4113367b6892c384c7bc49eb0772b3f00c899d2007fc3be6424676cdbab7522b00f6db550425c9c68e932b761f582061bda14
6
+ metadata.gz: 6856dc5fbe6411874444f3bc9ea16b4047ab11470d85b625daca9e054bc6289a37ac023a1fa899a6e9199aebfe1e6a2b1b4f3716611d34bc80ad829e0c57cf04
7
+ data.tar.gz: ab453e9b05648c4ce61feca5febcaf815d898b17f098696b27dff28d006d015130987d9d8233532b5b74d4fb5c31f6e3f7d12ca4fac10d6ac070bfbb5a4f384b
@@ -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
- render json: Spree.api.cart_serializer.new(@cart.reload, params: serializer_params).to_h, status: status
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 CouponCodesController < Store::BaseController
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/coupon_codes
13
- # Apply a coupon code to the cart
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/coupon_codes/:id
29
- # Remove a coupon code from the cart
30
- # :id is the coupon code string (e.g., SAVE10)
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
@@ -3,9 +3,11 @@ module Spree
3
3
  module V3
4
4
  module Store
5
5
  module Carts
6
- class PaymentsController < Store::ResourceController
6
+ class PaymentsController < Store::BaseController
7
7
  include Spree::Api::V3::CartResolvable
8
8
 
9
+ before_action :find_cart!
10
+
9
11
  # POST /api/v3/store/carts/:cart_id/payments
10
12
  # Creates a payment for non-session payment methods (e.g. Check, Cash on Delivery, Bank Transfer)
11
13
  def create
@@ -19,7 +21,7 @@ module Spree
19
21
  )
20
22
  end
21
23
 
22
- unless payment_method.available_for_order?(@parent)
24
+ unless payment_method.available_for_order?(@cart)
23
25
  return render_error(
24
26
  code: 'payment_method_unavailable',
25
27
  message: Spree.t('api.v3.payments.method_unavailable'),
@@ -27,43 +29,20 @@ module Spree
27
29
  )
28
30
  end
29
31
 
30
- amount = params[:amount].presence || @parent.total_minus_store_credits
32
+ amount = params[:amount].presence || @cart.total_minus_store_credits
31
33
 
32
- @payment = @parent.payments.build(
34
+ @payment = @cart.payments.build(
33
35
  payment_method: payment_method,
34
36
  amount: amount,
35
37
  metadata: params[:metadata].present? ? params[:metadata].to_unsafe_h : {}
36
38
  )
37
39
 
38
40
  if @payment.save
39
- render json: serialize_resource(@payment), status: :created
41
+ render json: Spree.api.payment_serializer.new(@payment, params: serializer_params).to_h, status: :created
40
42
  else
41
43
  render_errors(@payment.errors)
42
44
  end
43
45
  end
44
-
45
- protected
46
-
47
- def set_parent
48
- find_cart!
49
- @parent = @cart
50
- end
51
-
52
- def parent_association
53
- :payments
54
- end
55
-
56
- def model_class
57
- Spree::Payment
58
- end
59
-
60
- def serializer_class
61
- Spree.api.payment_serializer
62
- end
63
-
64
- # Authorization is handled by find_cart! in set_parent
65
- def authorize_resource!(*)
66
- end
67
46
  end
68
47
  end
69
48
  end
@@ -5,9 +5,9 @@ module Spree
5
5
  module Customer
6
6
  class AddressesController < ResourceController
7
7
  prepend_before_action :require_authentication!
8
- before_action :set_resource, only: [:show, :update, :destroy, :mark_as_default]
8
+ before_action :set_resource, only: [:show, :update, :destroy]
9
9
 
10
- # POST /api/v3/store/customer/addresses
10
+ # POST /api/v3/store/customers/me/addresses
11
11
  def create
12
12
  result = Spree.address_create_service.call(
13
13
  address_params: permitted_params,
@@ -21,25 +21,7 @@ module Spree
21
21
  end
22
22
  end
23
23
 
24
- # PATCH /api/v3/store/customer/addresses/:id/mark_as_default
25
- def mark_as_default
26
- kind = params[:kind].to_s
27
-
28
- unless %w[billing shipping].include?(kind)
29
- return render_error(
30
- code: ERROR_CODES[:invalid_request],
31
- message: 'kind must be billing or shipping',
32
- status: :unprocessable_content
33
- )
34
- end
35
-
36
- attribute = kind == 'billing' ? :bill_address_id : :ship_address_id
37
- current_user.update!(attribute => @resource.id)
38
-
39
- render json: serialize_resource(@resource.reload)
40
- end
41
-
42
- # PATCH /api/v3/store/customer/addresses/:id
24
+ # PATCH /api/v3/store/customers/me/addresses/:id
43
25
  def update
44
26
  result = Spree.address_update_service.call(
45
27
  address: @resource,
@@ -74,7 +56,8 @@ module Spree
74
56
  def permitted_params
75
57
  params.permit(
76
58
  :first_name, :last_name, :address1, :address2, :city,
77
- :postal_code, :phone, :company, :country_iso, :state_abbr, :state_name
59
+ :postal_code, :phone, :company, :country_iso, :state_abbr, :state_name,
60
+ :is_default_billing, :is_default_shipping
78
61
  )
79
62
  end
80
63
  end
@@ -13,7 +13,7 @@ module Spree
13
13
 
14
14
  skip_before_action :authenticate_user
15
15
 
16
- # POST /api/v3/store/customer/password_resets
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/customer/password_resets/:id
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,39 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Store
5
+ module Customer
6
+ class StoreCreditsController < ResourceController
7
+ prepend_before_action :require_authentication!
8
+
9
+ protected
10
+
11
+ def set_parent
12
+ @parent = current_user
13
+ end
14
+
15
+ def parent_association
16
+ :store_credits
17
+ end
18
+
19
+ def scope
20
+ super.for_store(current_store).where(currency: current_currency)
21
+ end
22
+
23
+ def model_class
24
+ Spree::StoreCredit
25
+ end
26
+
27
+ def serializer_class
28
+ Spree.api.store_credit_serializer
29
+ end
30
+
31
+ # Authorization is handled by set_parent scoping to current_user
32
+ def authorize_resource!(*)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -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
@@ -7,11 +7,11 @@ module Spree
7
7
  city: [:string, nullable: true], postal_code: [:string, nullable: true], phone: [:string, nullable: true],
8
8
  company: [:string, nullable: true], state_abbr: [:string, nullable: true], state_name: [:string, nullable: true],
9
9
  state_text: [:string, nullable: true], country_iso: :string, country_name: :string,
10
- quick_checkout: :boolean
10
+ quick_checkout: :boolean, is_default_billing: :boolean, is_default_shipping: :boolean
11
11
 
12
12
  attributes :first_name, :last_name, :full_name, :address1, :address2, :postal_code,
13
13
  :city, :phone, :company, :country_name, :country_iso, :state_text,
14
- :state_abbr, :quick_checkout
14
+ :state_abbr, :quick_checkout, :is_default_billing, :is_default_shipping
15
15
 
16
16
  # State name - used for countries without predefined states
17
17
  attribute :state_name do |address|
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ class GiftCardSerializer < V3::GiftCardSerializer
6
+ typelize metadata: 'Record<string, unknown> | null'
7
+
8
+ attribute :metadata do |gift_card|
9
+ gift_card.metadata.presence
10
+ end
11
+
12
+ attributes created_at: :iso8601, updated_at: :iso8601
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -50,6 +50,7 @@ module Spree
50
50
 
51
51
  one :billing_address, resource: Spree.api.admin_address_serializer, if: proc { expand?('billing_address') }
52
52
  one :shipping_address, resource: Spree.api.admin_address_serializer, if: proc { expand?('shipping_address') }
53
+ one :gift_card, resource: Spree.api.admin_gift_card_serializer
53
54
 
54
55
  many :payment_methods, resource: Spree.api.admin_payment_method_serializer, if: proc { expand?('payment_methods') }
55
56
 
@@ -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
@@ -14,9 +14,15 @@ module Spree
14
14
  tax_total: :string, display_tax_total: :string,
15
15
  included_tax_total: :string, display_included_tax_total: :string,
16
16
  additional_tax_total: :string, display_additional_tax_total: :string,
17
+ store_credit_total: :string, display_store_credit_total: :string,
18
+ gift_card_total: :string, display_gift_card_total: :string,
19
+ covered_by_store_credit: :boolean,
17
20
  total: :string, display_total: :string,
21
+ amount_due: :string, display_amount_due: :string,
18
22
  shipping_eq_billing_address: :boolean,
19
- billing_address: { nullable: true }, shipping_address: { nullable: true }
23
+ warnings: 'Array<{code: string, message: string, line_item_id?: string, variant_id?: string}>',
24
+ billing_address: { nullable: true }, shipping_address: { nullable: true },
25
+ gift_card: { nullable: true }
20
26
 
21
27
  # Override ID to use cart_ prefix
22
28
  attribute :id do |order|
@@ -30,9 +36,23 @@ module Spree
30
36
  :discount_total, :display_discount_total,
31
37
  :tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
32
38
  :additional_tax_total, :display_additional_tax_total, :total, :display_total,
33
- :delivery_total, :display_delivery_total,
39
+ :gift_card_total, :display_gift_card_total,
40
+ :amount_due, :display_amount_due,
41
+ :delivery_total, :display_delivery_total, :warnings,
34
42
  created_at: :iso8601, updated_at: :iso8601
35
43
 
44
+ attribute :store_credit_total do |order|
45
+ order.total_applied_store_credit.to_s
46
+ end
47
+
48
+ attribute :display_store_credit_total do |order|
49
+ order.display_total_applied_store_credit.to_s
50
+ end
51
+
52
+ attribute :covered_by_store_credit do |order|
53
+ order.covered_by_store_credit?
54
+ end
55
+
36
56
  attribute :current_step do |order|
37
57
  order.current_checkout_step
38
58
  end
@@ -57,6 +77,7 @@ module Spree
57
77
  one :shipping_address, resource: Spree.api.address_serializer
58
78
 
59
79
  many :payment_methods, resource: Spree.api.payment_method_serializer
80
+ one :gift_card, resource: Spree.api.gift_card_serializer
60
81
  end
61
82
  end
62
83
  end
@@ -6,11 +6,24 @@ module Spree
6
6
  class CustomerSerializer < BaseSerializer
7
7
  typelize email: :string, first_name: [:string, nullable: true], last_name: [:string, nullable: true],
8
8
  phone: [:string, nullable: true], accepts_email_marketing: :boolean,
9
+ available_store_credit_total: :string, display_available_store_credit_total: :string,
9
10
  default_billing_address: { nullable: true }, default_shipping_address: { nullable: true }
10
11
 
11
12
  attributes :email, :first_name, :last_name, :phone, :accepts_email_marketing,
12
13
  created_at: :iso8601, updated_at: :iso8601
13
14
 
15
+ attribute :available_store_credit_total do |user, params|
16
+ store = params&.dig(:store) || Spree::Current.store
17
+ currency = params&.dig(:currency) || Spree::Current.currency || store&.default_currency
18
+ user.total_available_store_credit(currency, store).to_s
19
+ end
20
+
21
+ attribute :display_available_store_credit_total do |user, params|
22
+ store = params&.dig(:store) || Spree::Current.store
23
+ currency = params&.dig(:currency) || Spree::Current.currency || store&.default_currency
24
+ Spree::Money.new(user.total_available_store_credit(currency, store), currency: currency).to_s
25
+ end
26
+
14
27
  many :addresses, resource: Spree.api.address_serializer
15
28
  one :bill_address, key: :default_billing_address, resource: Spree.api.address_serializer
16
29
  one :ship_address, key: :default_shipping_address, resource: Spree.api.address_serializer
@@ -14,8 +14,14 @@ module Spree
14
14
  tax_total: :string, display_tax_total: :string,
15
15
  included_tax_total: :string, display_included_tax_total: :string,
16
16
  additional_tax_total: :string, display_additional_tax_total: :string,
17
- total: :string, display_total: :string, completed_at: [:string, nullable: true],
18
- billing_address: { nullable: true }, shipping_address: { nullable: true }
17
+ store_credit_total: :string, display_store_credit_total: :string,
18
+ gift_card_total: :string, display_gift_card_total: :string,
19
+ covered_by_store_credit: :boolean,
20
+ total: :string, display_total: :string,
21
+ amount_due: :string, display_amount_due: :string,
22
+ completed_at: [:string, nullable: true],
23
+ billing_address: { nullable: true }, shipping_address: { nullable: true },
24
+ gift_card: { nullable: true }
19
25
 
20
26
  attributes :number, :email, :customer_note,
21
27
  :currency, :locale, :total_quantity,
@@ -24,15 +30,30 @@ module Spree
24
30
  :discount_total, :display_discount_total,
25
31
  :tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
26
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,
27
35
  :delivery_total, :display_delivery_total, :fulfillment_status, :payment_status,
28
36
  completed_at: :iso8601, created_at: :iso8601, updated_at: :iso8601
29
37
 
38
+ attribute :store_credit_total do |order|
39
+ order.total_applied_store_credit.to_s
40
+ end
41
+
42
+ attribute :display_store_credit_total do |order|
43
+ order.display_total_applied_store_credit.to_s
44
+ end
45
+
46
+ attribute :covered_by_store_credit do |order|
47
+ order.covered_by_store_credit?
48
+ end
49
+
30
50
  many :discounts, resource: Spree.api.discount_serializer
31
51
  many :line_items, key: :items, resource: Spree.api.line_item_serializer
32
52
  many :fulfillments, resource: Spree.api.fulfillment_serializer
33
53
  many :payments, resource: Spree.api.payment_serializer
34
54
  one :billing_address, resource: Spree.api.address_serializer
35
55
  one :shipping_address, resource: Spree.api.address_serializer
56
+ one :gift_card, resource: Spree.api.gift_card_serializer
36
57
  end
37
58
  end
38
59
  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
@@ -4,7 +4,7 @@ module Spree
4
4
  # Store API Product Serializer
5
5
  # Customer-facing product data with limited fields
6
6
  class ProductSerializer < BaseSerializer
7
- typelize name: :string, description: [:string, nullable: true], slug: :string,
7
+ typelize name: :string, description: [:string, nullable: true], description_html: [:string, nullable: true], slug: :string,
8
8
  meta_description: [:string, nullable: true], meta_keywords: [:string, nullable: true],
9
9
  variant_count: :number,
10
10
  default_variant_id: :string,
@@ -15,7 +15,7 @@ module Spree
15
15
  original_price: ['Price', nullable: true],
16
16
  tags: [:string, multi: true]
17
17
 
18
- attributes :name, :description, :slug,
18
+ attributes :name, :slug,
19
19
  :meta_description, :meta_keywords,
20
20
  :variant_count,
21
21
  available_on: :iso8601, created_at: :iso8601, updated_at: :iso8601
@@ -36,6 +36,16 @@ module Spree
36
36
  product.available?
37
37
  end
38
38
 
39
+ attribute :description do |product|
40
+ next if product.description.blank?
41
+
42
+ Nokogiri::HTML.fragment(product.description).text.squish
43
+ end
44
+
45
+ attribute :description_html do |product|
46
+ product.description
47
+ end
48
+
39
49
  attribute :default_variant_id do |product|
40
50
  product.default_variant&.prefixed_id
41
51
  end
@@ -101,6 +111,14 @@ module Spree
101
111
  key: :metafields,
102
112
  resource: Spree.api.metafield_serializer,
103
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
104
122
  end
105
123
  end
106
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,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: '%{model} 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
@@ -38,10 +38,10 @@ Spree::Core::Engine.add_routes do
38
38
  post :complete
39
39
  end
40
40
  resources :items, only: [:create, :update, :destroy], controller: 'carts/items'
41
- resources :coupon_codes, only: [:create, :destroy], controller: 'carts/coupon_codes'
42
- resources :fulfillments, only: [:index, :update], controller: 'carts/fulfillments'
43
- resources :payment_methods, only: [:index], controller: 'carts/payment_methods'
44
- resources :payments, only: [:index, :show, :create], controller: 'carts/payments'
41
+ resources :discount_codes, only: [:create, :destroy], controller: 'carts/discount_codes'
42
+ resources :gift_cards, only: [:create, :destroy], controller: 'carts/gift_cards'
43
+ resources :fulfillments, only: [:update], controller: 'carts/fulfillments'
44
+ resources :payments, only: [:create], controller: 'carts/payments'
45
45
  resources :payment_sessions, only: [:create, :show, :update], controller: 'carts/payment_sessions' do
46
46
  member do
47
47
  patch :complete
@@ -53,23 +53,25 @@ Spree::Core::Engine.add_routes do
53
53
  # Orders (single order lookup, guest-accessible via order token)
54
54
  resources :orders, only: [:show]
55
55
 
56
- # Customer (current user profile)
56
+ # Policies (return policy, privacy policy, terms of service, etc.)
57
+ resources :policies, only: [:index, :show]
58
+
59
+ # Password Resets (top-level, no auth required)
60
+ resources :password_resets, only: [:create, :update], controller: 'customer/password_resets'
61
+
62
+ # Customers
57
63
  resources :customers, only: [:create]
58
- get 'customer', to: 'customers#show'
59
- patch 'customer', to: 'customers#update'
60
64
 
61
- # Customer nested resources
62
- namespace :customer, path: 'customer' do
63
- resources :password_resets, only: [:create, :update]
65
+ # Current customer profile and nested resources (/customers/me/...)
66
+ namespace :customer, path: 'customers/me' do
67
+ get '/', action: :show, controller: '/spree/api/v3/store/customers'
68
+ patch '/', action: :update, controller: '/spree/api/v3/store/customers'
64
69
 
65
70
  resources :orders, only: [:index, :show]
66
- resources :addresses, only: [:index, :show, :create, :update, :destroy] do
67
- member do
68
- patch :mark_as_default
69
- end
70
- end
71
+ resources :addresses, only: [:index, :show, :create, :update, :destroy]
71
72
  resources :credit_cards, only: [:index, :show, :destroy]
72
73
  resources :gift_cards, only: [:index, :show]
74
+ resources :store_credits, only: [:index, :show]
73
75
  resources :payment_setup_sessions, only: [:create, :show] do
74
76
  member do
75
77
  patch :complete
@@ -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',
@@ -170,6 +173,7 @@ module Spree
170
173
  admin_stock_item_serializer: 'Spree::Api::V3::Admin::StockItemSerializer',
171
174
  admin_shipment_serializer: 'Spree::Api::V3::Admin::FulfillmentSerializer',
172
175
  admin_fulfillment_serializer: 'Spree::Api::V3::Admin::FulfillmentSerializer',
176
+ admin_gift_card_serializer: 'Spree::Api::V3::Admin::GiftCardSerializer',
173
177
  admin_payment_serializer: 'Spree::Api::V3::Admin::PaymentSerializer',
174
178
  admin_refund_serializer: 'Spree::Api::V3::Admin::RefundSerializer',
175
179
  admin_adjustment_serializer: 'Spree::Api::V3::Admin::AdjustmentSerializer',
@@ -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.rc1
4
+ version: 5.4.0.rc3
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-20 00:00:00.000000000 Z
11
+ date: 2026-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rswag-specs
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - '='
88
88
  - !ruby/object:Gem::Version
89
- version: 5.4.0.rc1
89
+ version: 5.4.0.rc3
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.rc1
96
+ version: 5.4.0.rc3
97
97
  description: Spree's API
98
98
  email:
99
99
  - hello@spreecommerce.org
@@ -121,10 +121,10 @@ 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/coupon_codes_controller.rb
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
- - app/controllers/spree/api/v3/store/carts/payment_methods_controller.rb
128
128
  - app/controllers/spree/api/v3/store/carts/payment_sessions_controller.rb
129
129
  - app/controllers/spree/api/v3/store/carts/payments_controller.rb
130
130
  - app/controllers/spree/api/v3/store/carts/store_credits_controller.rb
@@ -139,12 +139,14 @@ files:
139
139
  - app/controllers/spree/api/v3/store/customer/orders_controller.rb
140
140
  - app/controllers/spree/api/v3/store/customer/password_resets_controller.rb
141
141
  - app/controllers/spree/api/v3/store/customer/payment_setup_sessions_controller.rb
142
+ - app/controllers/spree/api/v3/store/customer/store_credits_controller.rb
142
143
  - app/controllers/spree/api/v3/store/customers_controller.rb
143
144
  - app/controllers/spree/api/v3/store/digitals_controller.rb
144
145
  - app/controllers/spree/api/v3/store/locales_controller.rb
145
146
  - app/controllers/spree/api/v3/store/markets/countries_controller.rb
146
147
  - app/controllers/spree/api/v3/store/markets_controller.rb
147
148
  - app/controllers/spree/api/v3/store/orders_controller.rb
149
+ - app/controllers/spree/api/v3/store/policies_controller.rb
148
150
  - app/controllers/spree/api/v3/store/products/filters_controller.rb
149
151
  - app/controllers/spree/api/v3/store/products_controller.rb
150
152
  - app/controllers/spree/api/v3/store/resource_controller.rb
@@ -167,6 +169,7 @@ files:
167
169
  - app/serializers/spree/api/v3/admin/digital_link_serializer.rb
168
170
  - app/serializers/spree/api/v3/admin/discount_serializer.rb
169
171
  - app/serializers/spree/api/v3/admin/fulfillment_serializer.rb
172
+ - app/serializers/spree/api/v3/admin/gift_card_serializer.rb
170
173
  - app/serializers/spree/api/v3/admin/line_item_serializer.rb
171
174
  - app/serializers/spree/api/v3/admin/media_serializer.rb
172
175
  - app/serializers/spree/api/v3/admin/metafield_serializer.rb
@@ -176,6 +179,7 @@ files:
176
179
  - app/serializers/spree/api/v3/admin/payment_method_serializer.rb
177
180
  - app/serializers/spree/api/v3/admin/payment_serializer.rb
178
181
  - app/serializers/spree/api/v3/admin/payment_source_serializer.rb
182
+ - app/serializers/spree/api/v3/admin/price_history_serializer.rb
179
183
  - app/serializers/spree/api/v3/admin/price_serializer.rb
180
184
  - app/serializers/spree/api/v3/admin/product_serializer.rb
181
185
  - app/serializers/spree/api/v3/admin/refund_serializer.rb
@@ -222,6 +226,8 @@ files:
222
226
  - app/serializers/spree/api/v3/payment_session_serializer.rb
223
227
  - app/serializers/spree/api/v3/payment_setup_session_serializer.rb
224
228
  - app/serializers/spree/api/v3/payment_source_serializer.rb
229
+ - app/serializers/spree/api/v3/policy_serializer.rb
230
+ - app/serializers/spree/api/v3/price_history_serializer.rb
225
231
  - app/serializers/spree/api/v3/price_serializer.rb
226
232
  - app/serializers/spree/api/v3/product_serializer.rb
227
233
  - app/serializers/spree/api/v3/promotion_serializer.rb
@@ -268,9 +274,9 @@ licenses:
268
274
  - BSD-3-Clause
269
275
  metadata:
270
276
  bug_tracker_uri: https://github.com/spree/spree/issues
271
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.rc1
277
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.rc3
272
278
  documentation_uri: https://docs.spreecommerce.org/
273
- source_code_uri: https://github.com/spree/spree/tree/v5.4.0.rc1
279
+ source_code_uri: https://github.com/spree/spree/tree/v5.4.0.rc3
274
280
  post_install_message:
275
281
  rdoc_options: []
276
282
  require_paths:
@@ -1,24 +0,0 @@
1
- module Spree
2
- module Api
3
- module V3
4
- module Store
5
- module Carts
6
- class PaymentMethodsController < Store::BaseController
7
- include Spree::Api::V3::CartResolvable
8
-
9
- before_action :find_cart!
10
-
11
- # GET /api/v3/store/carts/:cart_id/payment_methods
12
- def index
13
- methods = @cart.collect_frontend_payment_methods
14
- render json: {
15
- data: methods.map { |m| Spree.api.payment_method_serializer.new(m, params: serializer_params).to_h },
16
- meta: { count: methods.size }
17
- }
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end