spree_api 5.4.0.beta8 → 5.4.0.beta9

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v3/error_handler.rb +1 -0
  3. data/app/controllers/spree/api/v3/store/auth_controller.rb +62 -19
  4. data/app/controllers/spree/api/v3/store/carts/{shipments_controller.rb → fulfillments_controller.rb} +13 -13
  5. data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +2 -0
  6. data/app/controllers/spree/api/v3/store/customers_controller.rb +6 -2
  7. data/app/serializers/spree/api/v3/admin/address_serializer.rb +2 -2
  8. data/app/serializers/spree/api/v3/admin/credit_card_serializer.rb +2 -2
  9. data/app/serializers/spree/api/v3/admin/{shipping_method_serializer.rb → delivery_method_serializer.rb} +1 -1
  10. data/app/serializers/spree/api/v3/admin/delivery_rate_serializer.rb +11 -0
  11. data/app/serializers/spree/api/v3/admin/{shipment_serializer.rb → fulfillment_serializer.rb} +3 -3
  12. data/app/serializers/spree/api/v3/admin/order_serializer.rb +5 -4
  13. data/app/serializers/spree/api/v3/admin/product_serializer.rb +0 -5
  14. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +2 -2
  15. data/app/serializers/spree/api/v3/cart_serializer.rb +11 -3
  16. data/app/serializers/spree/api/v3/{shipping_method_serializer.rb → delivery_method_serializer.rb} +1 -1
  17. data/app/serializers/spree/api/v3/{shipping_rate_serializer.rb → delivery_rate_serializer.rb} +4 -4
  18. data/app/serializers/spree/api/v3/fulfillment_serializer.rb +45 -0
  19. data/app/serializers/spree/api/v3/gift_card_serializer.rb +2 -2
  20. data/app/serializers/spree/api/v3/newsletter_subscriber_serializer.rb +2 -2
  21. data/app/serializers/spree/api/v3/order_serializer.rb +21 -5
  22. data/app/serializers/spree/api/v3/payment_serializer.rb +6 -2
  23. data/app/serializers/spree/api/v3/product_serializer.rb +0 -5
  24. data/app/serializers/spree/api/v3/variant_serializer.rb +2 -2
  25. data/config/routes.rb +2 -1
  26. data/lib/spree/api/configuration.rb +1 -0
  27. data/lib/spree/api/dependencies.rb +12 -6
  28. data/lib/spree/api/openapi/schema_helper.rb +31 -1
  29. metadata +13 -13
  30. data/app/serializers/spree/api/v3/admin/shipping_rate_serializer.rb +0 -11
  31. data/app/serializers/spree/api/v3/shipment_serializer.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d59f56e428252697b4360183c26b92d20fe7607630b84589a5203ad766d6a467
4
- data.tar.gz: 2807c39fa35138ae1db42e5fb184998dfc8e9c25d7309f11b43edc0af1751877
3
+ metadata.gz: 7630000b76a8d6596d6a94e2e5ef3b928ad55035995a777d79a855da0b83e707
4
+ data.tar.gz: 5621ebbdb257e9c69518e3ad71a9368954c0fbf7db6ad7a3b4adb237b00cb83b
5
5
  SHA512:
6
- metadata.gz: ed23df8b1714945bb9b4718f0c81443fb140fb3ddbaf48e72fbc4de1f9b6b0f9b41373587cf57f69fcc4e928aa75fc1636c7dc57b700e24c9958a3bd70c42bc0
7
- data.tar.gz: bc499f9d970dc0fc0d24de5c711070b267e103d60c82704e5de1f48c7bcd635183cf024e1cfef4b0f9d0841f5e2ea150e74618f7ec3283dd59ee73f20059682f
6
+ metadata.gz: ecdbc7f8b2e6792a1e8641ebaabfcc69423705f032435bb960ff3f2ae36eb19c89dc224b8af79278b3e74536ca95112442139278e847cca9710f437300d9395e
7
+ data.tar.gz: 8264257bdf243e8bc504880ceed1f99233baaf09bf6e7264714ab9395b59429e0c9c553269012015d9e870dd3dc797b19224d204b7a33518681878533200e71e
@@ -12,6 +12,7 @@ module Spree
12
12
  access_denied: 'access_denied',
13
13
  invalid_token: 'invalid_token',
14
14
  invalid_provider: 'invalid_provider',
15
+ invalid_refresh_token: 'invalid_refresh_token',
15
16
  current_password_invalid: 'current_password_invalid',
16
17
  password_reset_token_invalid: 'password_reset_token_invalid',
17
18
  redirect_url_not_allowed: 'redirect_url_not_allowed',
@@ -7,9 +7,9 @@ module Spree
7
7
  rate_limit to: Spree::Api::Config[:rate_limit_login], within: Spree::Api::Config[:rate_limit_window].seconds, store: Rails.cache, only: :create, with: RATE_LIMIT_RESPONSE
8
8
  rate_limit to: Spree::Api::Config[:rate_limit_refresh], within: Spree::Api::Config[:rate_limit_window].seconds, store: Rails.cache, only: :refresh, with: RATE_LIMIT_RESPONSE
9
9
  rate_limit to: Spree::Api::Config[:rate_limit_oauth], within: Spree::Api::Config[:rate_limit_window].seconds, store: Rails.cache, only: :oauth_callback, with: RATE_LIMIT_RESPONSE
10
+ rate_limit to: Spree::Api::Config[:rate_limit_refresh], within: Spree::Api::Config[:rate_limit_window].seconds, store: Rails.cache, only: :logout, with: RATE_LIMIT_RESPONSE
10
11
 
11
- skip_before_action :authenticate_user, only: [:create, :oauth_callback]
12
- prepend_before_action :require_authentication!, only: [:refresh]
12
+ skip_before_action :authenticate_user, only: [:create, :refresh, :oauth_callback]
13
13
 
14
14
  # POST /api/v3/store/auth/login
15
15
  # Supports multiple authentication providers via :provider param
@@ -23,11 +23,7 @@ module Spree
23
23
 
24
24
  if result.success?
25
25
  user = result.value
26
- token = generate_jwt(user)
27
- render json: {
28
- token: token,
29
- user: user_serializer.new(user, params: serializer_params).to_h
30
- }
26
+ render json: auth_response(user)
31
27
  else
32
28
  render_error(
33
29
  code: ERROR_CODES[:authentication_failed],
@@ -38,21 +34,55 @@ module Spree
38
34
  end
39
35
 
40
36
  # POST /api/v3/store/auth/refresh
37
+ # Accepts: { "refresh_token": "rt_xxx" }
38
+ # Returns new access JWT + rotated refresh token
41
39
  def refresh
42
- token = generate_jwt(current_user)
40
+ refresh_token_value = params[:refresh_token]
41
+
42
+ if refresh_token_value.blank?
43
+ return render_error(
44
+ code: ERROR_CODES[:invalid_refresh_token],
45
+ message: 'refresh_token is required',
46
+ status: :unauthorized
47
+ )
48
+ end
49
+
50
+ refresh_token = Spree::RefreshToken.active.find_by(token: refresh_token_value)
51
+
52
+ if refresh_token.nil?
53
+ return render_error(
54
+ code: ERROR_CODES[:invalid_refresh_token],
55
+ message: 'Invalid or expired refresh token',
56
+ status: :unauthorized
57
+ )
58
+ end
59
+
60
+ user = refresh_token.user
61
+ new_refresh_token = refresh_token.rotate!(request_env: request_env_for_token)
62
+
43
63
  render json: {
44
- token: token,
45
- user: user_serializer.new(current_user, params: serializer_params).to_h
64
+ token: generate_jwt(user),
65
+ refresh_token: new_refresh_token.token,
66
+ user: user_serializer.new(user, params: serializer_params).to_h
46
67
  }
47
68
  end
48
69
 
70
+ # POST /api/v3/store/auth/logout
71
+ # Accepts: { "refresh_token": "rt_xxx" }
72
+ # Revokes the refresh token
73
+ def logout
74
+ refresh_token_value = params[:refresh_token]
75
+
76
+ if refresh_token_value.present?
77
+ Spree::RefreshToken.find_by(token: refresh_token_value)&.destroy
78
+ end
79
+
80
+ head :no_content
81
+ end
82
+
49
83
  # POST /api/v3/store/auth/oauth/callback
50
84
  # OAuth callback endpoint for server-side OAuth flows
51
85
  def oauth_callback
52
- # This endpoint is designed for OAuth flows where the server
53
- # exchanges the authorization code for an access token
54
- # For client-side flows, use the regular /login endpoint with id_token
55
-
56
86
  strategy = authentication_strategy
57
87
  return unless strategy # Error already rendered by determine_strategy
58
88
 
@@ -60,11 +90,7 @@ module Spree
60
90
 
61
91
  if result.success?
62
92
  user = result.value
63
- token = generate_jwt(user)
64
- render json: {
65
- token: token,
66
- user: user_serializer.new(user, params: serializer_params).to_h
67
- }
93
+ render json: auth_response(user)
68
94
  else
69
95
  render_error(
70
96
  code: ERROR_CODES[:authentication_failed],
@@ -88,6 +114,23 @@ module Spree
88
114
 
89
115
  private
90
116
 
117
+ def auth_response(user)
118
+ refresh_token = Spree::RefreshToken.create_for(user, request_env: request_env_for_token)
119
+
120
+ {
121
+ token: generate_jwt(user),
122
+ refresh_token: refresh_token.token,
123
+ user: user_serializer.new(user, params: serializer_params).to_h
124
+ }
125
+ end
126
+
127
+ def request_env_for_token
128
+ {
129
+ ip_address: request.remote_ip,
130
+ user_agent: request.user_agent&.truncate(255)
131
+ }
132
+ end
133
+
91
134
  def authentication_strategy
92
135
  strategy_class = determine_strategy
93
136
  strategy_class.new(
@@ -3,31 +3,31 @@ module Spree
3
3
  module V3
4
4
  module Store
5
5
  module Carts
6
- class ShipmentsController < Store::BaseController
6
+ class FulfillmentsController < 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
- # GET /api/v3/store/carts/:cart_id/shipments
12
+ # GET /api/v3/store/carts/:cart_id/fulfillments
13
13
  def index
14
- shipments = @cart.shipments.includes(shipping_rates: :shipping_method)
14
+ fulfillments = @cart.shipments.includes(shipping_rates: :shipping_method)
15
15
  render json: {
16
- data: shipments.map { |s| Spree.api.shipment_serializer.new(s, params: serializer_params).to_h },
17
- meta: { count: shipments.size }
16
+ data: fulfillments.map { |s| Spree.api.fulfillment_serializer.new(s, params: serializer_params).to_h },
17
+ meta: { count: fulfillments.size }
18
18
  }
19
19
  end
20
20
 
21
- # PATCH /api/v3/store/carts/:cart_id/shipments/:id
22
- # Select a shipping rate for a specific shipment
21
+ # PATCH /api/v3/store/carts/:cart_id/fulfillments/:id
22
+ # Select a delivery rate for a specific fulfillment
23
23
  def update
24
24
  with_order_lock do
25
- shipment = @cart.shipments.find_by_prefix_id!(params[:id])
25
+ fulfillment = @cart.shipments.find_by_prefix_id!(params[:id])
26
26
 
27
- if permitted_params[:selected_shipping_rate_id].present?
28
- shipping_rate = shipment.shipping_rates.find_by_prefix_id!(permitted_params[:selected_shipping_rate_id])
29
- shipment.selected_shipping_rate_id = shipping_rate.id
30
- shipment.save!
27
+ if permitted_params[:selected_delivery_rate_id].present?
28
+ delivery_rate = fulfillment.shipping_rates.find_by_prefix_id!(permitted_params[:selected_delivery_rate_id])
29
+ fulfillment.selected_shipping_rate_id = delivery_rate.id
30
+ fulfillment.save!
31
31
  end
32
32
 
33
33
  # Auto-advance (e.g. delivery → payment) after rate selection.
@@ -41,7 +41,7 @@ module Spree
41
41
  private
42
42
 
43
43
  def permitted_params
44
- params.permit(:selected_shipping_rate_id)
44
+ params.permit(:selected_delivery_rate_id)
45
45
  end
46
46
 
47
47
  # Temporary — Spree 6 removes the checkout state machine.
@@ -55,10 +55,12 @@ module Spree
55
55
 
56
56
  if user.update(password: params[:password], password_confirmation: params[:password_confirmation])
57
57
  jwt = generate_jwt(user)
58
+ refresh_token = Spree::RefreshToken.create_for(user, request_env: { ip_address: request.remote_ip, user_agent: request.user_agent&.truncate(255) })
58
59
  user.publish_event('customer.password_reset')
59
60
 
60
61
  render json: {
61
62
  token: jwt,
63
+ refresh_token: refresh_token.token,
62
64
  user: serializer_class.new(user, params: serializer_params).to_h
63
65
  }
64
66
  else
@@ -13,9 +13,13 @@ module Spree
13
13
  user = Spree.user_class.new(create_params)
14
14
 
15
15
  if user.save
16
- token = generate_jwt(user)
16
+ refresh_token = Spree::RefreshToken.create_for(user, request_env: {
17
+ ip_address: request.remote_ip,
18
+ user_agent: request.user_agent&.truncate(255)
19
+ })
17
20
  render json: {
18
- token: token,
21
+ token: generate_jwt(user),
22
+ refresh_token: refresh_token.token,
19
23
  user: user_serializer.new(user, params: serializer_params).to_h
20
24
  }, status: :created
21
25
  else
@@ -4,13 +4,13 @@ module Spree
4
4
  module Admin
5
5
  class AddressSerializer < V3::AddressSerializer
6
6
  typelize label: [:string, nullable: true],
7
- user_id: [:string, nullable: true],
7
+ customer_id: [:string, nullable: true],
8
8
  metadata: 'Record<string, unknown> | null'
9
9
 
10
10
  attributes :label,
11
11
  created_at: :iso8601, updated_at: :iso8601
12
12
 
13
- attribute :user_id do |address|
13
+ attribute :customer_id do |address|
14
14
  address.user&.prefixed_id
15
15
  end
16
16
 
@@ -3,10 +3,10 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class CreditCardSerializer < V3::CreditCardSerializer
6
- typelize user_id: [:string, nullable: true],
6
+ typelize customer_id: [:string, nullable: true],
7
7
  payment_method_id: [:string, nullable: true]
8
8
 
9
- attribute :user_id do |credit_card|
9
+ attribute :customer_id do |credit_card|
10
10
  credit_card.user&.prefixed_id
11
11
  end
12
12
 
@@ -2,7 +2,7 @@ module Spree
2
2
  module Api
3
3
  module V3
4
4
  module Admin
5
- class ShippingMethodSerializer < V3::ShippingMethodSerializer
5
+ class DeliveryMethodSerializer < V3::DeliveryMethodSerializer
6
6
  attributes created_at: :iso8601, updated_at: :iso8601
7
7
  end
8
8
  end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ class DeliveryRateSerializer < V3::DeliveryRateSerializer
6
+ one :shipping_method, key: :delivery_method, resource: Spree.api.admin_delivery_method_serializer, if: proc { expand?('delivery_method') }
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,7 +2,7 @@ module Spree
2
2
  module Api
3
3
  module V3
4
4
  module Admin
5
- class ShipmentSerializer < V3::ShipmentSerializer
5
+ class FulfillmentSerializer < V3::FulfillmentSerializer
6
6
  typelize metadata: 'Record<string, unknown> | null',
7
7
  order_id: [:string, nullable: true],
8
8
  stock_location_id: [:string, nullable: true],
@@ -26,9 +26,9 @@ module Spree
26
26
  end
27
27
 
28
28
  # Override inherited associations to use admin serializers
29
- one :shipping_method, resource: Spree.api.admin_shipping_method_serializer, if: proc { expand?('shipping_method') }
29
+ one :shipping_method, key: :delivery_method, resource: Spree.api.admin_delivery_method_serializer, if: proc { expand?('delivery_method') }
30
30
  one :stock_location, resource: Spree.api.admin_stock_location_serializer, if: proc { expand?('stock_location') }
31
- many :shipping_rates, resource: Spree.api.admin_shipping_rate_serializer, if: proc { expand?('shipping_rates') }
31
+ many :shipping_rates, key: :delivery_rates, resource: Spree.api.admin_delivery_rate_serializer, if: proc { expand?('delivery_rates') }
32
32
 
33
33
  one :order,
34
34
  resource: Spree.api.admin_order_serializer,
@@ -11,7 +11,7 @@ module Spree
11
11
  store_owner_notification_delivered: :boolean,
12
12
  internal_note: [:string, nullable: true], approver_id: [:string, nullable: true],
13
13
  canceler_id: [:string, nullable: true], created_by_id: [:string, nullable: true],
14
- user_id: [:string, nullable: true],
14
+ customer_id: [:string, nullable: true],
15
15
  canceled_at: [:string, nullable: true], approved_at: [:string, nullable: true],
16
16
  payment_total: :string, display_payment_total: :string,
17
17
  metadata: 'Record<string, unknown> | null'
@@ -38,14 +38,14 @@ module Spree
38
38
  order.created_by&.prefixed_id
39
39
  end
40
40
 
41
- attribute :user_id do |order|
41
+ attribute :customer_id do |order|
42
42
  order.user&.prefixed_id
43
43
  end
44
44
 
45
45
  # Override inherited associations to use admin serializers
46
46
  many :order_promotions, key: :promotions, resource: Spree.api.admin_order_promotion_serializer, if: proc { expand?('promotions') }
47
47
  many :line_items, key: :items, resource: Spree.api.admin_line_item_serializer, if: proc { expand?('items') }
48
- many :shipments, resource: Spree.api.admin_shipment_serializer, if: proc { expand?('shipments') }
48
+ many :shipments, key: :fulfillments, resource: Spree.api.admin_fulfillment_serializer, if: proc { expand?('fulfillments') }
49
49
  many :payments, resource: Spree.api.admin_payment_serializer, if: proc { expand?('payments') }
50
50
 
51
51
  one :bill_address, resource: Spree.api.admin_address_serializer, if: proc { expand?('bill_address') }
@@ -54,8 +54,9 @@ module Spree
54
54
  many :payment_methods, resource: Spree.api.admin_payment_method_serializer, if: proc { expand?('payment_methods') }
55
55
 
56
56
  one :user,
57
+ key: :customer,
57
58
  resource: Spree.api.admin_customer_serializer,
58
- if: proc { expand?('user') }
59
+ if: proc { expand?('customer') }
59
60
 
60
61
  one :approver,
61
62
  resource: Spree.api.admin_customer_serializer,
@@ -32,11 +32,6 @@ module Spree
32
32
  resource: Spree.api.admin_variant_serializer,
33
33
  if: proc { expand?('default_variant') }
34
34
 
35
- one :master,
36
- key: :master_variant,
37
- resource: Spree.api.admin_variant_serializer,
38
- if: proc { expand?('master_variant') }
39
-
40
35
  one :primary_media,
41
36
  resource: Spree.api.admin_media_serializer,
42
37
  if: proc { expand?('primary_media') }
@@ -3,11 +3,11 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class StoreCreditSerializer < V3::StoreCreditSerializer
6
- typelize user_id: [:string, nullable: true],
6
+ typelize customer_id: [:string, nullable: true],
7
7
  created_by_id: [:string, nullable: true],
8
8
  metadata: 'Record<string, unknown> | null'
9
9
 
10
- attribute :user_id do |store_credit|
10
+ attribute :customer_id do |store_credit|
11
11
  store_credit.user&.prefixed_id
12
12
  end
13
13
 
@@ -8,7 +8,7 @@ module Spree
8
8
  special_instructions: [:string, nullable: true], currency: :string, locale: [:string, nullable: true], item_count: :number,
9
9
  requirements: 'Array<{step: string, field: string, message: string}>',
10
10
  item_total: :string, display_item_total: :string,
11
- ship_total: :string, display_ship_total: :string,
11
+ delivery_total: :string, display_delivery_total: :string,
12
12
  adjustment_total: :string, display_adjustment_total: :string,
13
13
  promo_total: :string, display_promo_total: :string,
14
14
  tax_total: :string, display_tax_total: :string,
@@ -24,12 +24,20 @@ module Spree
24
24
 
25
25
  attributes :number, :token, :email, :special_instructions,
26
26
  :currency, :locale, :item_count,
27
- :item_total, :display_item_total, :ship_total, :display_ship_total,
27
+ :item_total, :display_item_total,
28
28
  :adjustment_total, :display_adjustment_total, :promo_total, :display_promo_total,
29
29
  :tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
30
30
  :additional_tax_total, :display_additional_tax_total, :total, :display_total,
31
31
  created_at: :iso8601, updated_at: :iso8601
32
32
 
33
+ attribute :delivery_total do |order|
34
+ order.ship_total
35
+ end
36
+
37
+ attribute :display_delivery_total do |order|
38
+ order.display_ship_total.to_s
39
+ end
40
+
33
41
  attribute :current_step do |order|
34
42
  order.current_checkout_step
35
43
  end
@@ -44,7 +52,7 @@ module Spree
44
52
 
45
53
  many :cart_promotions, key: :promotions, resource: Spree.api.cart_promotion_serializer
46
54
  many :line_items, key: :items, resource: Spree.api.line_item_serializer
47
- many :shipments, resource: Spree.api.shipment_serializer
55
+ many :shipments, key: :fulfillments, resource: Spree.api.fulfillment_serializer
48
56
  many :payments, resource: Spree.api.payment_serializer
49
57
  one :bill_address, resource: Spree.api.address_serializer
50
58
  one :ship_address, resource: Spree.api.address_serializer
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  module Api
3
3
  module V3
4
- class ShippingMethodSerializer < BaseSerializer
4
+ class DeliveryMethodSerializer < BaseSerializer
5
5
  typelize name: :string, code: [:string, nullable: true]
6
6
 
7
7
  attributes :name, :code
@@ -1,11 +1,11 @@
1
1
  module Spree
2
2
  module Api
3
3
  module V3
4
- class ShippingRateSerializer < BaseSerializer
5
- typelize name: :string, selected: :boolean, shipping_method_id: :string,
4
+ class DeliveryRateSerializer < BaseSerializer
5
+ typelize name: :string, selected: :boolean, delivery_method_id: :string,
6
6
  cost: :string, display_cost: :string
7
7
 
8
- attribute :shipping_method_id do |shipping_rate|
8
+ attribute :delivery_method_id do |shipping_rate|
9
9
  shipping_rate.shipping_method&.prefixed_id
10
10
  end
11
11
 
@@ -15,7 +15,7 @@ module Spree
15
15
  shipping_rate.display_cost.to_s
16
16
  end
17
17
 
18
- one :shipping_method, resource: Spree.api.shipping_method_serializer
18
+ one :shipping_method, key: :delivery_method, resource: Spree.api.delivery_method_serializer
19
19
  end
20
20
  end
21
21
  end
@@ -0,0 +1,45 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class FulfillmentSerializer < BaseSerializer
5
+ typelize number: :string, status: :string, fulfillment_type: :string,
6
+ tracking: [:string, nullable: true],
7
+ tracking_url: [:string, nullable: true], fulfilled_at: [:string, nullable: true],
8
+ cost: :string, display_cost: :string,
9
+ items: 'Array<{ item_id: string; variant_id: string; quantity: number }>'
10
+
11
+ attributes :number, :tracking, :tracking_url,
12
+ :cost, :display_cost,
13
+ created_at: :iso8601, updated_at: :iso8601
14
+
15
+ attribute :status do |shipment|
16
+ shipment.state
17
+ end
18
+
19
+ attribute :fulfillment_type do |shipment|
20
+ shipment.digital? ? 'digital' : 'shipping'
21
+ end
22
+
23
+ attribute :fulfilled_at do |shipment|
24
+ shipment.shipped_at&.iso8601
25
+ end
26
+
27
+ # Which items (and how many) are in this fulfillment.
28
+ # A line item can be split across fulfillments with different quantities.
29
+ attribute :items do |shipment|
30
+ shipment.manifest.map do |item|
31
+ {
32
+ item_id: item.line_item.prefixed_id,
33
+ variant_id: item.variant.prefixed_id,
34
+ quantity: item.quantity
35
+ }
36
+ end
37
+ end
38
+
39
+ one :shipping_method, key: :delivery_method, resource: Spree.api.delivery_method_serializer
40
+ one :stock_location, resource: Spree.api.stock_location_serializer
41
+ many :shipping_rates, key: :delivery_rates, resource: Spree.api.delivery_rate_serializer
42
+ end
43
+ end
44
+ end
45
+ end
@@ -3,7 +3,7 @@ module Spree
3
3
  module V3
4
4
  class GiftCardSerializer < BaseSerializer
5
5
  typelize code: :string,
6
- state: :string,
6
+ status: :string,
7
7
  amount: :string,
8
8
  amount_used: :string,
9
9
  amount_authorized: :string,
@@ -21,7 +21,7 @@ module Spree
21
21
  gift_card.display_code
22
22
  end
23
23
 
24
- attribute :state do |gift_card|
24
+ attribute :status do |gift_card|
25
25
  gift_card.display_state
26
26
  end
27
27
 
@@ -6,7 +6,7 @@ module Spree
6
6
  class NewsletterSubscriberSerializer < BaseSerializer
7
7
  typelize email: :string, verified: :boolean,
8
8
  verified_at: [:string, nullable: true],
9
- user_id: [:string, nullable: true]
9
+ customer_id: [:string, nullable: true]
10
10
 
11
11
  attributes :email, created_at: :iso8601, updated_at: :iso8601
12
12
 
@@ -18,7 +18,7 @@ module Spree
18
18
  subscriber.verified_at&.iso8601
19
19
  end
20
20
 
21
- attribute :user_id do |subscriber|
21
+ attribute :customer_id do |subscriber|
22
22
  subscriber.user&.prefixed_id
23
23
  end
24
24
  end
@@ -6,9 +6,9 @@ module Spree
6
6
  class OrderSerializer < BaseSerializer
7
7
  typelize number: :string, email: :string,
8
8
  special_instructions: [:string, nullable: true], currency: :string, locale: [:string, nullable: true], item_count: :number,
9
- shipment_state: [:string, nullable: true], payment_state: [:string, nullable: true],
9
+ fulfillment_status: [:string, nullable: true], payment_status: [:string, nullable: true],
10
10
  item_total: :string, display_item_total: :string,
11
- ship_total: :string, display_ship_total: :string,
11
+ delivery_total: :string, display_delivery_total: :string,
12
12
  adjustment_total: :string, display_adjustment_total: :string,
13
13
  promo_total: :string, display_promo_total: :string,
14
14
  tax_total: :string, display_tax_total: :string,
@@ -18,16 +18,32 @@ module Spree
18
18
  bill_address: { nullable: true }, ship_address: { nullable: true }
19
19
 
20
20
  attributes :number, :email, :special_instructions,
21
- :currency, :locale, :item_count, :shipment_state, :payment_state,
22
- :item_total, :display_item_total, :ship_total, :display_ship_total,
21
+ :currency, :locale, :item_count,
22
+ :item_total, :display_item_total,
23
23
  :adjustment_total, :display_adjustment_total, :promo_total, :display_promo_total,
24
24
  :tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
25
25
  :additional_tax_total, :display_additional_tax_total, :total, :display_total,
26
26
  completed_at: :iso8601, created_at: :iso8601, updated_at: :iso8601
27
27
 
28
+ attribute :fulfillment_status do |order|
29
+ order.shipment_state
30
+ end
31
+
32
+ attribute :payment_status do |order|
33
+ order.payment_state
34
+ end
35
+
36
+ attribute :delivery_total do |order|
37
+ order.ship_total
38
+ end
39
+
40
+ attribute :display_delivery_total do |order|
41
+ order.display_ship_total.to_s
42
+ end
43
+
28
44
  many :order_promotions, key: :promotions, resource: Spree.api.order_promotion_serializer
29
45
  many :line_items, key: :items, resource: Spree.api.line_item_serializer
30
- many :shipments, resource: Spree.api.shipment_serializer
46
+ many :shipments, key: :fulfillments, resource: Spree.api.fulfillment_serializer
31
47
  many :payments, resource: Spree.api.payment_serializer
32
48
  one :bill_address, resource: Spree.api.address_serializer
33
49
  one :ship_address, resource: Spree.api.address_serializer
@@ -2,7 +2,7 @@ module Spree
2
2
  module Api
3
3
  module V3
4
4
  class PaymentSerializer < BaseSerializer
5
- typelize state: :string, payment_method_id: :string, response_code: [:string, nullable: true],
5
+ typelize status: :string, payment_method_id: :string, response_code: [:string, nullable: true],
6
6
  number: :string, amount: :string, display_amount: :string,
7
7
  source_type: [:string, nullable: true, enum: %w[credit_card store_credit payment_source]],
8
8
  source_id: [:string, nullable: true],
@@ -12,9 +12,13 @@ module Spree
12
12
  payment.payment_method&.prefixed_id
13
13
  end
14
14
 
15
- attributes :state, :response_code, :number, :amount, :display_amount,
15
+ attributes :response_code, :number, :amount, :display_amount,
16
16
  created_at: :iso8601, updated_at: :iso8601
17
17
 
18
+ attribute :status do |payment|
19
+ payment.state
20
+ end
21
+
18
22
  attribute :source_type do |payment|
19
23
  case payment.source_type
20
24
  when 'Spree::CreditCard'
@@ -85,11 +85,6 @@ module Spree
85
85
  resource: Spree.api.variant_serializer,
86
86
  if: proc { expand?('default_variant') }
87
87
 
88
- one :master,
89
- key: :master_variant,
90
- resource: Spree.api.variant_serializer,
91
- if: proc { expand?('master_variant') }
92
-
93
88
  many :option_types,
94
89
  resource: Spree.api.option_type_serializer,
95
90
  if: proc { expand?('option_types') }
@@ -5,7 +5,7 @@ module Spree
5
5
  # Customer-facing variant data with limited fields
6
6
  class VariantSerializer < BaseSerializer
7
7
  typelize product_id: :string, sku: [:string, nullable: true],
8
- is_master: :boolean, options_text: :string, track_inventory: :boolean, media_count: :number,
8
+ options_text: :string, track_inventory: :boolean, media_count: :number,
9
9
  thumbnail_url: [:string, nullable: true],
10
10
  purchasable: :boolean, in_stock: :boolean, backorderable: :boolean,
11
11
  weight: [:number, nullable: true], height: [:number, nullable: true], width: [:number, nullable: true], depth: [:number, nullable: true],
@@ -16,7 +16,7 @@ module Spree
16
16
  variant.product&.prefixed_id
17
17
  end
18
18
 
19
- attributes :sku, :is_master, :options_text, :track_inventory, :media_count,
19
+ attributes :sku, :options_text, :track_inventory, :media_count,
20
20
  created_at: :iso8601, updated_at: :iso8601
21
21
 
22
22
  # Main variant image URL for listings (cached primary_media)
data/config/routes.rb CHANGED
@@ -5,6 +5,7 @@ Spree::Core::Engine.add_routes do
5
5
  # Authentication
6
6
  post 'auth/login', to: 'auth#create'
7
7
  post 'auth/refresh', to: 'auth#refresh'
8
+ post 'auth/logout', to: 'auth#logout'
8
9
  post 'auth/oauth/callback', to: 'auth#oauth_callback'
9
10
 
10
11
  # Markets
@@ -38,7 +39,7 @@ Spree::Core::Engine.add_routes do
38
39
  end
39
40
  resources :items, only: [:create, :update, :destroy], controller: 'carts/items'
40
41
  resources :coupon_codes, only: [:create, :destroy], controller: 'carts/coupon_codes'
41
- resources :shipments, only: [:index, :update], controller: 'carts/shipments'
42
+ resources :fulfillments, only: [:index, :update], controller: 'carts/fulfillments'
42
43
  resources :payment_methods, only: [:index], controller: 'carts/payment_methods'
43
44
  resources :payments, only: [:index, :show, :create], controller: 'carts/payments'
44
45
  resources :payment_sessions, only: [:create, :show, :update], controller: 'carts/payment_sessions' do
@@ -11,6 +11,7 @@ module Spree
11
11
 
12
12
  preference :jwt_expiration, :integer, default: 3600 # 1 hour in seconds
13
13
  preference :jwt_secret_key, :string, default: nil
14
+ preference :refresh_token_expiry, :integer, default: 2_592_000 # 30 days in seconds
14
15
 
15
16
  # Rate limiting
16
17
  preference :rate_limit_per_key, :integer, default: 300 # per publishable API key
@@ -107,7 +107,8 @@ module Spree
107
107
  payment_setup_session_serializer: 'Spree::Api::V3::PaymentSetupSessionSerializer',
108
108
  payment_source_serializer: 'Spree::Api::V3::PaymentSourceSerializer',
109
109
  store_credit_serializer: 'Spree::Api::V3::StoreCreditSerializer',
110
- shipment_serializer: 'Spree::Api::V3::ShipmentSerializer',
110
+ shipment_serializer: 'Spree::Api::V3::FulfillmentSerializer',
111
+ fulfillment_serializer: 'Spree::Api::V3::FulfillmentSerializer',
111
112
  address_serializer: 'Spree::Api::V3::AddressSerializer',
112
113
  customer_serializer: 'Spree::Api::V3::CustomerSerializer',
113
114
  country_serializer: 'Spree::Api::V3::CountrySerializer',
@@ -116,8 +117,10 @@ module Spree
116
117
  wishlist_serializer: 'Spree::Api::V3::WishlistSerializer',
117
118
  wished_item_serializer: 'Spree::Api::V3::WishedItemSerializer',
118
119
  payment_method_serializer: 'Spree::Api::V3::PaymentMethodSerializer',
119
- shipping_method_serializer: 'Spree::Api::V3::ShippingMethodSerializer',
120
- shipping_rate_serializer: 'Spree::Api::V3::ShippingRateSerializer',
120
+ shipping_method_serializer: 'Spree::Api::V3::DeliveryMethodSerializer',
121
+ shipping_rate_serializer: 'Spree::Api::V3::DeliveryRateSerializer',
122
+ delivery_method_serializer: 'Spree::Api::V3::DeliveryMethodSerializer',
123
+ delivery_rate_serializer: 'Spree::Api::V3::DeliveryRateSerializer',
121
124
  stock_location_serializer: 'Spree::Api::V3::StockLocationSerializer',
122
125
  category_serializer: 'Spree::Api::V3::CategorySerializer',
123
126
  cart_promotion_serializer: 'Spree::Api::V3::CartPromotionSerializer',
@@ -164,7 +167,8 @@ module Spree
164
167
  admin_media_serializer: 'Spree::Api::V3::Admin::MediaSerializer',
165
168
  admin_asset_serializer: 'Spree::Api::V3::Admin::AssetSerializer',
166
169
  admin_stock_item_serializer: 'Spree::Api::V3::Admin::StockItemSerializer',
167
- admin_shipment_serializer: 'Spree::Api::V3::Admin::ShipmentSerializer',
170
+ admin_shipment_serializer: 'Spree::Api::V3::Admin::FulfillmentSerializer',
171
+ admin_fulfillment_serializer: 'Spree::Api::V3::Admin::FulfillmentSerializer',
168
172
  admin_payment_serializer: 'Spree::Api::V3::Admin::PaymentSerializer',
169
173
  admin_refund_serializer: 'Spree::Api::V3::Admin::RefundSerializer',
170
174
  admin_adjustment_serializer: 'Spree::Api::V3::Admin::AdjustmentSerializer',
@@ -174,9 +178,11 @@ module Spree
174
178
  admin_reimbursement_serializer: 'Spree::Api::V3::Admin::ReimbursementSerializer',
175
179
  admin_admin_user_serializer: 'Spree::Api::V3::Admin::AdminUserSerializer',
176
180
  admin_address_serializer: 'Spree::Api::V3::Admin::AddressSerializer',
177
- admin_shipping_method_serializer: 'Spree::Api::V3::Admin::ShippingMethodSerializer',
181
+ admin_shipping_method_serializer: 'Spree::Api::V3::Admin::DeliveryMethodSerializer',
182
+ admin_delivery_method_serializer: 'Spree::Api::V3::Admin::DeliveryMethodSerializer',
178
183
  admin_stock_location_serializer: 'Spree::Api::V3::Admin::StockLocationSerializer',
179
- admin_shipping_rate_serializer: 'Spree::Api::V3::Admin::ShippingRateSerializer',
184
+ admin_shipping_rate_serializer: 'Spree::Api::V3::Admin::DeliveryRateSerializer',
185
+ admin_delivery_rate_serializer: 'Spree::Api::V3::Admin::DeliveryRateSerializer',
180
186
  admin_order_promotion_serializer: 'Spree::Api::V3::Admin::OrderPromotionSerializer',
181
187
  admin_payment_method_serializer: 'Spree::Api::V3::Admin::PaymentMethodSerializer',
182
188
  admin_credit_card_serializer: 'Spree::Api::V3::Admin::CreditCardSerializer',
@@ -81,9 +81,10 @@ module Spree
81
81
  type: :object,
82
82
  properties: {
83
83
  token: { type: :string, description: 'JWT access token' },
84
+ refresh_token: { type: :string, description: 'Refresh token for obtaining new access tokens' },
84
85
  user: { '$ref' => '#/components/schemas/Customer' }
85
86
  },
86
- required: %w[token user]
87
+ required: %w[token refresh_token user]
87
88
  },
88
89
  CheckoutRequirement: {
89
90
  type: :object,
@@ -93,6 +94,16 @@ module Spree
93
94
  message: { type: :string, description: 'Human-readable requirement message', example: 'Add a payment method' }
94
95
  },
95
96
  required: %w[step field message]
97
+ },
98
+ FulfillmentManifestItem: {
99
+ type: :object,
100
+ description: 'An item within a fulfillment — which line item and how many units are in this fulfillment',
101
+ properties: {
102
+ item_id: { type: :string, description: 'Line item prefixed ID', example: 'li_abc123' },
103
+ variant_id: { type: :string, description: 'Variant prefixed ID', example: 'variant_abc123' },
104
+ quantity: { type: :integer, description: 'Quantity in this fulfillment', example: 2 }
105
+ },
106
+ required: %w[item_id variant_id quantity]
96
107
  }
97
108
  }
98
109
  end
@@ -120,6 +131,7 @@ module Spree
120
131
  strip_null_from_enums(s)
121
132
  end
122
133
  patch_cart_schema(schemas)
134
+ patch_fulfillment_schema(schemas)
123
135
  schemas
124
136
  end
125
137
  end
@@ -142,6 +154,24 @@ module Spree
142
154
  end
143
155
  end
144
156
 
157
+ # Typelizer cannot represent Array<{...}> inline object types in OpenAPI,
158
+ # so we patch Fulfillment#items to reference the FulfillmentManifestItem component schema.
159
+ def patch_fulfillment_schema(schemas)
160
+ fulfillment = schemas['Fulfillment'] || schemas[:Fulfillment]
161
+ return unless fulfillment
162
+
163
+ props = fulfillment[:properties]
164
+ return unless props
165
+
166
+ items_key = props.key?('items') ? 'items' : :items
167
+ if props[items_key]
168
+ props[items_key] = {
169
+ type: :array,
170
+ items: { '$ref' => '#/components/schemas/FulfillmentManifestItem' }
171
+ }
172
+ end
173
+ end
174
+
145
175
  # Typelizer adds nil to enum arrays for nullable fields.
146
176
  # OpenAPI 3.0 handles nullability via `nullable: true`, so the nil entry is redundant
147
177
  # and causes issues with code generators.
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.beta8
4
+ version: 5.4.0.beta9
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-16 00:00:00.000000000 Z
11
+ date: 2026-03-17 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.beta8
89
+ version: 5.4.0.beta9
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.beta8
96
+ version: 5.4.0.beta9
97
97
  description: Spree's API
98
98
  email:
99
99
  - hello@spreecommerce.org
@@ -121,11 +121,11 @@ files:
121
121
  - app/controllers/spree/api/v3/store/auth_controller.rb
122
122
  - app/controllers/spree/api/v3/store/base_controller.rb
123
123
  - app/controllers/spree/api/v3/store/carts/coupon_codes_controller.rb
124
+ - app/controllers/spree/api/v3/store/carts/fulfillments_controller.rb
124
125
  - app/controllers/spree/api/v3/store/carts/items_controller.rb
125
126
  - app/controllers/spree/api/v3/store/carts/payment_methods_controller.rb
126
127
  - app/controllers/spree/api/v3/store/carts/payment_sessions_controller.rb
127
128
  - app/controllers/spree/api/v3/store/carts/payments_controller.rb
128
- - app/controllers/spree/api/v3/store/carts/shipments_controller.rb
129
129
  - app/controllers/spree/api/v3/store/carts/store_credits_controller.rb
130
130
  - app/controllers/spree/api/v3/store/carts_controller.rb
131
131
  - app/controllers/spree/api/v3/store/categories/products_controller.rb
@@ -161,7 +161,10 @@ files:
161
161
  - app/serializers/spree/api/v3/admin/category_serializer.rb
162
162
  - app/serializers/spree/api/v3/admin/credit_card_serializer.rb
163
163
  - app/serializers/spree/api/v3/admin/customer_serializer.rb
164
+ - app/serializers/spree/api/v3/admin/delivery_method_serializer.rb
165
+ - app/serializers/spree/api/v3/admin/delivery_rate_serializer.rb
164
166
  - app/serializers/spree/api/v3/admin/digital_link_serializer.rb
167
+ - app/serializers/spree/api/v3/admin/fulfillment_serializer.rb
165
168
  - app/serializers/spree/api/v3/admin/line_item_serializer.rb
166
169
  - app/serializers/spree/api/v3/admin/media_serializer.rb
167
170
  - app/serializers/spree/api/v3/admin/metafield_serializer.rb
@@ -177,10 +180,7 @@ files:
177
180
  - app/serializers/spree/api/v3/admin/refund_serializer.rb
178
181
  - app/serializers/spree/api/v3/admin/reimbursement_serializer.rb
179
182
  - app/serializers/spree/api/v3/admin/return_authorization_serializer.rb
180
- - app/serializers/spree/api/v3/admin/shipment_serializer.rb
181
183
  - app/serializers/spree/api/v3/admin/shipping_category_serializer.rb
182
- - app/serializers/spree/api/v3/admin/shipping_method_serializer.rb
183
- - app/serializers/spree/api/v3/admin/shipping_rate_serializer.rb
184
184
  - app/serializers/spree/api/v3/admin/stock_item_serializer.rb
185
185
  - app/serializers/spree/api/v3/admin/stock_location_serializer.rb
186
186
  - app/serializers/spree/api/v3/admin/store_credit_serializer.rb
@@ -196,9 +196,12 @@ files:
196
196
  - app/serializers/spree/api/v3/currency_serializer.rb
197
197
  - app/serializers/spree/api/v3/customer_return_serializer.rb
198
198
  - app/serializers/spree/api/v3/customer_serializer.rb
199
+ - app/serializers/spree/api/v3/delivery_method_serializer.rb
200
+ - app/serializers/spree/api/v3/delivery_rate_serializer.rb
199
201
  - app/serializers/spree/api/v3/digital_link_serializer.rb
200
202
  - app/serializers/spree/api/v3/digital_serializer.rb
201
203
  - app/serializers/spree/api/v3/export_serializer.rb
204
+ - app/serializers/spree/api/v3/fulfillment_serializer.rb
202
205
  - app/serializers/spree/api/v3/gift_card_batch_serializer.rb
203
206
  - app/serializers/spree/api/v3/gift_card_serializer.rb
204
207
  - app/serializers/spree/api/v3/import_row_serializer.rb
@@ -227,10 +230,7 @@ files:
227
230
  - app/serializers/spree/api/v3/report_serializer.rb
228
231
  - app/serializers/spree/api/v3/return_authorization_serializer.rb
229
232
  - app/serializers/spree/api/v3/return_item_serializer.rb
230
- - app/serializers/spree/api/v3/shipment_serializer.rb
231
233
  - app/serializers/spree/api/v3/shipping_category_serializer.rb
232
- - app/serializers/spree/api/v3/shipping_method_serializer.rb
233
- - app/serializers/spree/api/v3/shipping_rate_serializer.rb
234
234
  - app/serializers/spree/api/v3/state_serializer.rb
235
235
  - app/serializers/spree/api/v3/stock_item_serializer.rb
236
236
  - app/serializers/spree/api/v3/stock_location_serializer.rb
@@ -268,9 +268,9 @@ licenses:
268
268
  - BSD-3-Clause
269
269
  metadata:
270
270
  bug_tracker_uri: https://github.com/spree/spree/issues
271
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.beta8
271
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.beta9
272
272
  documentation_uri: https://docs.spreecommerce.org/
273
- source_code_uri: https://github.com/spree/spree/tree/v5.4.0.beta8
273
+ source_code_uri: https://github.com/spree/spree/tree/v5.4.0.beta9
274
274
  post_install_message:
275
275
  rdoc_options: []
276
276
  require_paths:
@@ -1,11 +0,0 @@
1
- module Spree
2
- module Api
3
- module V3
4
- module Admin
5
- class ShippingRateSerializer < V3::ShippingRateSerializer
6
- one :shipping_method, resource: Spree.api.admin_shipping_method_serializer, if: proc { expand?('shipping_method') }
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,19 +0,0 @@
1
- module Spree
2
- module Api
3
- module V3
4
- class ShipmentSerializer < BaseSerializer
5
- typelize number: :string, state: :string, tracking: [:string, nullable: true],
6
- tracking_url: [:string, nullable: true], shipped_at: [:string, nullable: true],
7
- cost: :string, display_cost: :string
8
-
9
- attributes :number, :state, :tracking, :tracking_url,
10
- :cost, :display_cost,
11
- shipped_at: :iso8601, created_at: :iso8601, updated_at: :iso8601
12
-
13
- one :shipping_method, resource: Spree.api.shipping_method_serializer
14
- one :stock_location, resource: Spree.api.stock_location_serializer
15
- many :shipping_rates, resource: Spree.api.shipping_rate_serializer
16
- end
17
- end
18
- end
19
- end