spree_api 5.5.3 → 5.6.0.rc1

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v3/admin/role_grant_guard.rb +3 -1
  3. data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +1 -1
  4. data/app/controllers/concerns/spree/api/v3/channel_resolution.rb +4 -2
  5. data/app/controllers/concerns/spree/api/v3/locale_and_currency.rb +4 -1
  6. data/app/controllers/concerns/spree/api/v3/storefront_gating.rb +65 -0
  7. data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +1 -1
  8. data/app/controllers/spree/api/v3/admin/categories/products_controller.rb +98 -0
  9. data/app/controllers/spree/api/v3/admin/categories_controller.rb +102 -2
  10. data/app/controllers/spree/api/v3/admin/customers_controller.rb +8 -2
  11. data/app/controllers/spree/api/v3/admin/exports_controller.rb +3 -1
  12. data/app/controllers/spree/api/v3/admin/import_rows_controller.rb +49 -0
  13. data/app/controllers/spree/api/v3/admin/imports_controller.rb +274 -0
  14. data/app/controllers/spree/api/v3/admin/locales_controller.rb +29 -0
  15. data/app/controllers/spree/api/v3/admin/me_controller.rb +39 -9
  16. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +62 -2
  17. data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +105 -0
  18. data/app/controllers/spree/api/v3/admin/payment_methods_controller.rb +1 -8
  19. data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +1 -1
  20. data/app/controllers/spree/api/v3/admin/products_controller.rb +8 -7
  21. data/app/controllers/spree/api/v3/admin/promotions_controller.rb +1 -1
  22. data/app/controllers/spree/api/v3/admin/resource_controller.rb +31 -0
  23. data/app/controllers/spree/api/v3/admin/store_controller.rb +3 -0
  24. data/app/controllers/spree/api/v3/admin/translatable_resources_controller.rb +38 -0
  25. data/app/controllers/spree/api/v3/admin/translations/batches_controller.rb +113 -0
  26. data/app/controllers/spree/api/v3/admin/translations_controller.rb +106 -0
  27. data/app/controllers/spree/api/v3/resource_controller.rb +0 -2
  28. data/app/controllers/spree/api/v3/store/auth_controller.rb +3 -1
  29. data/app/controllers/spree/api/v3/store/base_controller.rb +5 -0
  30. data/app/controllers/spree/api/v3/store/carts/fulfillments_controller.rb +1 -3
  31. data/app/controllers/spree/api/v3/store/carts_controller.rb +4 -0
  32. data/app/controllers/spree/api/v3/store/countries_controller.rb +1 -0
  33. data/app/controllers/spree/api/v3/store/currencies_controller.rb +1 -0
  34. data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +5 -1
  35. data/app/controllers/spree/api/v3/store/customers_controller.rb +3 -1
  36. data/app/controllers/spree/api/v3/store/digitals_controller.rb +1 -0
  37. data/app/controllers/spree/api/v3/store/locales_controller.rb +3 -8
  38. data/app/controllers/spree/api/v3/store/markets/countries_controller.rb +1 -0
  39. data/app/controllers/spree/api/v3/store/markets_controller.rb +1 -0
  40. data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +1 -0
  41. data/app/controllers/spree/api/v3/store/products/filters_controller.rb +5 -2
  42. data/app/controllers/spree/api/v3/store/products_controller.rb +1 -1
  43. data/app/controllers/spree/api/v3/store/resource_controller.rb +1 -0
  44. data/app/serializers/concerns/spree/api/v3/admin/translatable.rb +38 -0
  45. data/app/serializers/spree/api/v3/admin/admin_user_serializer.rb +7 -1
  46. data/app/serializers/spree/api/v3/admin/category_serializer.rb +5 -3
  47. data/app/serializers/spree/api/v3/admin/channel_serializer.rb +5 -1
  48. data/app/serializers/spree/api/v3/admin/customer_serializer.rb +7 -2
  49. data/app/serializers/spree/api/v3/admin/discount_serializer.rb +4 -0
  50. data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +9 -0
  51. data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +7 -0
  52. data/app/serializers/spree/api/v3/admin/import_mapping_serializer.rb +20 -0
  53. data/app/serializers/spree/api/v3/admin/import_row_serializer.rb +17 -0
  54. data/app/serializers/spree/api/v3/admin/import_serializer.rb +70 -0
  55. data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +11 -0
  56. data/app/serializers/spree/api/v3/admin/option_type_serializer.rb +2 -0
  57. data/app/serializers/spree/api/v3/admin/order_serializer.rb +15 -1
  58. data/app/serializers/spree/api/v3/admin/payment_serializer.rb +4 -0
  59. data/app/serializers/spree/api/v3/admin/product_serializer.rb +1 -0
  60. data/app/serializers/spree/api/v3/admin/promotion_rule_serializer.rb +8 -0
  61. data/app/serializers/spree/api/v3/admin/resource_translations_serializer.rb +76 -0
  62. data/app/serializers/spree/api/v3/admin/setup_task_serializer.rb +18 -0
  63. data/app/serializers/spree/api/v3/admin/store_serializer.rb +27 -1
  64. data/app/serializers/spree/api/v3/admin/variant_serializer.rb +8 -1
  65. data/app/serializers/spree/api/v3/base_serializer.rb +13 -0
  66. data/app/serializers/spree/api/v3/cart_serializer.rb +25 -22
  67. data/app/serializers/spree/api/v3/discount_serializer.rb +6 -2
  68. data/app/serializers/spree/api/v3/fulfillment_serializer.rb +16 -13
  69. data/app/serializers/spree/api/v3/gift_card_serializer.rb +15 -11
  70. data/app/serializers/spree/api/v3/import_row_serializer.rb +1 -1
  71. data/app/serializers/spree/api/v3/line_item_serializer.rb +35 -17
  72. data/app/serializers/spree/api/v3/locale_serializer.rb +6 -1
  73. data/app/serializers/spree/api/v3/order_serializer.rb +25 -21
  74. data/app/serializers/spree/api/v3/payment_serializer.rb +6 -2
  75. data/app/serializers/spree/api/v3/product_serializer.rb +9 -1
  76. data/app/serializers/spree/api/v3/variant_serializer.rb +12 -1
  77. data/app/services/spree/api/v3/filters_aggregator.rb +4 -4
  78. data/app/subscribers/spree/webhook_event_subscriber.rb +6 -0
  79. data/config/initializers/typelizer.rb +10 -0
  80. data/config/locales/en.yml +4 -0
  81. data/config/routes.rb +54 -4
  82. data/lib/spree/api/dependencies.rb +5 -0
  83. data/lib/spree/api/openapi/schema_helper.rb +29 -0
  84. metadata +21 -6
@@ -3,6 +3,15 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class FulfillmentSerializer < V3::FulfillmentSerializer
6
+ # The Admin API has no guest gating — money fields inherited from the
7
+ # store serializer are always present, so override their nullability.
8
+ typelize cost: [:string, nullable: false], display_cost: [:string, nullable: false],
9
+ total: [:string, nullable: false], display_total: [:string, nullable: false],
10
+ discount_total: [:string, nullable: false], display_discount_total: [:string, nullable: false],
11
+ additional_tax_total: [:string, nullable: false], display_additional_tax_total: [:string, nullable: false],
12
+ included_tax_total: [:string, nullable: false], display_included_tax_total: [:string, nullable: false],
13
+ tax_total: [:string, nullable: false], display_tax_total: [:string, nullable: false]
14
+
6
15
  typelize metadata: 'Record<string, unknown>',
7
16
  order_id: [:string, nullable: true],
8
17
  stock_location_id: [:string, nullable: true],
@@ -6,6 +6,13 @@ module Spree
6
6
  # context the admin UI needs: who the card was issued to (customer),
7
7
  # who issued it (admin), and the orders that consumed it.
8
8
  class GiftCardSerializer < V3::GiftCardSerializer
9
+ # The Admin API has no guest gating — money fields inherited from the
10
+ # store serializer are always present, so override their nullability.
11
+ typelize amount: [:string, nullable: false], amount_used: [:string, nullable: false],
12
+ amount_authorized: [:string, nullable: false], amount_remaining: [:string, nullable: false],
13
+ display_amount: [:string, nullable: false], display_amount_used: [:string, nullable: false],
14
+ display_amount_remaining: [:string, nullable: false]
15
+
9
16
  typelize customer_id: [:string, nullable: true],
10
17
  created_by_id: [:string, nullable: true]
11
18
 
@@ -0,0 +1,20 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Admin API serializer for {Spree::ImportMapping}. The
6
+ # `complete_mapping` endpoint accepts exactly these attribute names
7
+ # back (read/write symmetry).
8
+ class ImportMappingSerializer < V3::BaseSerializer
9
+ typelize schema_field: :string,
10
+ file_column: [:string, nullable: true],
11
+ required: :boolean
12
+
13
+ attributes :schema_field, :file_column
14
+
15
+ attribute :required, &:required?
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Admin API serializer for {Spree::ImportRow}. Adds the raw CSV row
6
+ # (`data`) so the failure report can show the offending values.
7
+ class ImportRowSerializer < V3::ImportRowSerializer
8
+ typelize data: 'Record<string, string | null>'
9
+
10
+ attribute :data do |row|
11
+ row.data_json
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,70 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Admin API serializer for {Spree::Import} — drives the SPA import
6
+ # wizard: mapping payload while `mapping`, poll counters afterwards.
7
+ class ImportSerializer < V3::ImportSerializer
8
+ typelize completed_rows_count: :number,
9
+ failed_rows_count: :number,
10
+ processing_errors: [:string, nullable: true],
11
+ preferred_delimiter: :string,
12
+ schema_fields: 'Array<{ name: string; label: string; required: boolean }>',
13
+ csv_headers: [:string, multi: true],
14
+ sample_row: 'Record<string, string | null>',
15
+ original_filename: [:string, nullable: true],
16
+ original_byte_size: [:number, nullable: true],
17
+ original_file_url: [:string, nullable: true]
18
+
19
+ attributes :processing_errors, :preferred_delimiter
20
+
21
+ # The originally uploaded file — the audit trail. `original_file_url`
22
+ # is our own streaming endpoint (JWT-authenticated), not a signed
23
+ # ActiveStorage URL; see ExportSerializer#download_url.
24
+ attribute :original_filename do |import|
25
+ import.attachment.blob&.filename&.to_s if import.attachment.attached?
26
+ end
27
+
28
+ attribute :original_byte_size do |import|
29
+ import.attachment.blob&.byte_size if import.attachment.attached?
30
+ end
31
+
32
+ attribute :original_file_url do |import|
33
+ next nil unless import.attachment.attached?
34
+
35
+ Spree::Core::Engine.routes.url_helpers.download_api_v3_admin_import_path(
36
+ id: import.prefixed_id
37
+ )
38
+ end
39
+
40
+ attribute :completed_rows_count do |import|
41
+ import.rows_status_counts['completed'] || 0
42
+ end
43
+
44
+ attribute :failed_rows_count do |import|
45
+ import.rows_status_counts['failed'] || 0
46
+ end
47
+
48
+ attribute :schema_fields do |import|
49
+ import.schema_fields.map do |field|
50
+ { name: field[:name], label: field[:label], required: field[:required].present? }
51
+ end
52
+ end
53
+
54
+ # Mapping-state only: these read the attached blob, which the 2s
55
+ # processing poll must never do.
56
+ attribute :csv_headers do |import|
57
+ import.mapping? ? (import.csv_headers || []) : []
58
+ end
59
+
60
+ attribute :sample_row do |import|
61
+ import.mapping? ? import.sample_row : {}
62
+ end
63
+
64
+ many :mappings,
65
+ resource: proc { Spree.api.admin_import_mapping_serializer }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -5,6 +5,17 @@ module Spree
5
5
  # Admin API Line Item Serializer
6
6
  # Extends the store serializer with metadata visibility
7
7
  class LineItemSerializer < V3::LineItemSerializer
8
+ # The Admin API has no guest gating — money fields inherited from the
9
+ # store serializer are always present, so override their nullability.
10
+ typelize price: [:string, nullable: false], display_price: [:string, nullable: false],
11
+ total: [:string, nullable: false], display_total: [:string, nullable: false],
12
+ adjustment_total: [:string, nullable: false], display_adjustment_total: [:string, nullable: false],
13
+ additional_tax_total: [:string, nullable: false], display_additional_tax_total: [:string, nullable: false],
14
+ included_tax_total: [:string, nullable: false], display_included_tax_total: [:string, nullable: false],
15
+ discount_total: [:string, nullable: false], display_discount_total: [:string, nullable: false],
16
+ pre_tax_amount: [:string, nullable: false], display_pre_tax_amount: [:string, nullable: false],
17
+ discounted_amount: [:string, nullable: false], display_discounted_amount: [:string, nullable: false]
18
+
8
19
  typelize metadata: 'Record<string, unknown>',
9
20
  cost_price: [:string, nullable: true],
10
21
  tax_category_id: [:string, nullable: true]
@@ -3,6 +3,8 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class OptionTypeSerializer < V3::OptionTypeSerializer
6
+ include Spree::Api::V3::Admin::Translatable
7
+
6
8
  typelize metadata: 'Record<string, unknown>', filterable: :boolean
7
9
 
8
10
  attributes :metadata, :filterable,
@@ -6,6 +6,20 @@ module Spree
6
6
  # Full order data including admin-only fields
7
7
  class OrderSerializer < V3::OrderSerializer
8
8
 
9
+ # The Admin API has no guest gating — money fields inherited from the
10
+ # store serializer are always present, so override their nullability.
11
+ typelize item_total: [:string, nullable: false], display_item_total: [:string, nullable: false],
12
+ delivery_total: [:string, nullable: false], display_delivery_total: [:string, nullable: false],
13
+ adjustment_total: [:string, nullable: false], display_adjustment_total: [:string, nullable: false],
14
+ discount_total: [:string, nullable: false], display_discount_total: [:string, nullable: false],
15
+ tax_total: [:string, nullable: false], display_tax_total: [:string, nullable: false],
16
+ included_tax_total: [:string, nullable: false], display_included_tax_total: [:string, nullable: false],
17
+ additional_tax_total: [:string, nullable: false], display_additional_tax_total: [:string, nullable: false],
18
+ store_credit_total: [:string, nullable: false], display_store_credit_total: [:string, nullable: false],
19
+ gift_card_total: [:string, nullable: false], display_gift_card_total: [:string, nullable: false],
20
+ total: [:string, nullable: false], display_total: [:string, nullable: false],
21
+ amount_due: [:string, nullable: false], display_amount_due: [:string, nullable: false]
22
+
9
23
  typelize status: :string,
10
24
  last_ip_address: [:string, nullable: true],
11
25
  considered_risky: :boolean, confirmation_delivered: :boolean,
@@ -31,7 +45,7 @@ module Spree
31
45
  end
32
46
 
33
47
  attribute :tags do |order|
34
- order.tag_list.to_a
48
+ order.tags.map(&:name) # not pluck as we preload tags
35
49
  end
36
50
 
37
51
  attribute :internal_note do |order|
@@ -3,6 +3,10 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class PaymentSerializer < V3::PaymentSerializer
6
+ # The Admin API has no guest gating — money fields inherited from the
7
+ # store serializer are always present, so override their nullability.
8
+ typelize amount: [:string, nullable: false], display_amount: [:string, nullable: false]
9
+
6
10
  typelize metadata: 'Record<string, unknown>',
7
11
  captured_amount: :string,
8
12
  order_id: [:string, nullable: true],
@@ -6,6 +6,7 @@ module Spree
6
6
  # Full product data including admin-only fields
7
7
  # Extends the store serializer with additional attributes
8
8
  class ProductSerializer < V3::ProductSerializer
9
+ include Spree::Api::V3::Admin::Translatable
9
10
 
10
11
  typelize status: :string,
11
12
  tax_category_id: [:string, nullable: true],
@@ -78,6 +78,14 @@ module Spree
78
78
  many :countries,
79
79
  resource: proc { Spree.api.admin_country_serializer },
80
80
  if: proc { |rule| rule.respond_to?(:countries) }
81
+
82
+ many :channels,
83
+ resource: Spree.api.admin_channel_serializer,
84
+ if: proc { |rule| rule.respond_to?(:channels) }
85
+
86
+ many :markets,
87
+ resource: Spree.api.admin_market_serializer,
88
+ if: proc { |rule| rule.respond_to?(:markets) }
81
89
  end
82
90
  end
83
91
  end
@@ -0,0 +1,76 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Serializes a translatable record's translation payload — the locale ×
6
+ # field matrix, optionally with self-describing discovery +fields+, the
7
+ # locale envelope, and nested translatable +children+.
8
+ #
9
+ # Params select the shape:
10
+ # - +fields: true+ — include +fields+ (key/type/source) and nested
11
+ # +children+ (e.g. an option type's option values). The read endpoint
12
+ # (`GET …/:id/translations`) uses this so an editor fetches the whole
13
+ # tree in one request.
14
+ # - +envelope: true+ — include +default_locale+/+supported_locales+ from
15
+ # the record's own translatable store (so they can't contradict the
16
+ # matrix). The top-level read node and each batch-write echo carry it;
17
+ # nested children do not.
18
+ #
19
+ # The batch write echo passes +fields: false+ (matrix only); the read
20
+ # endpoint passes both true at the top level and re-renders children
21
+ # with +envelope: false+.
22
+ #
23
+ # Deliberately does NOT include Typelizer::DSL: the payload is computed
24
+ # (matrix/fields/children resolve to `unknown`), so the admin-sdk ships
25
+ # richer hand-written types in `src/types/translations.ts` instead.
26
+ # Without the DSL, Typelizer never enumerates this serializer.
27
+ class ResourceTranslationsSerializer
28
+ include Alba::Resource
29
+
30
+ attribute :resource_id, &:prefixed_id
31
+
32
+ attribute :resource_type do |record|
33
+ Spree::Translations.public_resource_type(record.class)
34
+ end
35
+
36
+ attribute :translations do |record|
37
+ Spree::Translations.matrix_for(record)
38
+ end
39
+
40
+ attribute :fields, if: proc { params[:fields] } do |record|
41
+ Spree::Translations.fields_for(record)
42
+ end
43
+
44
+ attribute :default_locale, if: proc { params[:envelope] } do |record|
45
+ locale_store(record).default_locale
46
+ end
47
+
48
+ attribute :supported_locales, if: proc { params[:envelope] } do |record|
49
+ locale_store(record).supported_locales_list
50
+ end
51
+
52
+ attribute :children, if: proc { |record| params[:fields] && children_for(record).any? } do |record|
53
+ # Children carry their own fields + matrix, but not the locale
54
+ # envelope (that's a property of the top-level request, not each row).
55
+ # Recurse with self.class so a swapped subclass renders children too.
56
+ child_params = params.merge(envelope: false)
57
+ children_for(record).map { |child| self.class.new(child, params: child_params).to_h }
58
+ end
59
+
60
+ private
61
+
62
+ # The store whose locales the payload reports — the record's own
63
+ # translatable store, falling back to the current store.
64
+ def locale_store(record)
65
+ record.translatable_store || params[:store]
66
+ end
67
+
68
+ def children_for(record)
69
+ assoc = record.class.try(:translatable_children)
70
+ assoc.present? ? Array(record.public_send(assoc)) : []
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Serializes {Spree::SetupTask} — an evaluated onboarding checklist
6
+ # entry. A value object, so no id.
7
+ class SetupTaskSerializer
8
+ include Alba::Resource
9
+ include Typelizer::DSL
10
+
11
+ typelize name: :string, done: :boolean
12
+
13
+ attributes :name, :done
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -3,10 +3,12 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class StoreSerializer < V3::BaseSerializer
6
- typelize name: :string, url: :string,
6
+ typelize name: :string, url: :string, code: :string, api_url: :string,
7
+ preferred_storefront_url: [:string, nullable: true],
7
8
  default_currency: :string, default_locale: :string,
8
9
  supported_currencies: [:string, multi: true],
9
10
  supported_locales: [:string, multi: true],
11
+ available_locales: [:string, multi: true],
10
12
  logo_url: [:string, nullable: true],
11
13
  mailer_logo_url: [:string, nullable: true],
12
14
  mail_from_address: [:string, nullable: true],
@@ -17,10 +19,14 @@ module Spree
17
19
  preferred_timezone: :string,
18
20
  preferred_weight_unit: :string,
19
21
  preferred_unit_system: :string,
22
+ preferred_storefront_access: :string,
23
+ preferred_guest_checkout: :boolean,
20
24
  metadata: 'Record<string, unknown>'
21
25
 
22
26
  attributes :metadata,
23
27
  :name,
28
+ :code,
29
+ :preferred_storefront_url,
24
30
  :default_currency,
25
31
  :default_locale,
26
32
  :mail_from_address,
@@ -31,16 +37,36 @@ module Spree
31
37
  :preferred_timezone,
32
38
  :preferred_weight_unit,
33
39
  :preferred_unit_system,
40
+ :preferred_storefront_access,
41
+ :preferred_guest_checkout,
34
42
  created_at: :iso8601, updated_at: :iso8601
35
43
 
36
44
  attribute :url, &:storefront_url
37
45
 
46
+ # The backend's own public URL — what a headless client sets as its
47
+ # Store API endpoint (distinct from `url`, the storefront's URL).
48
+ attribute :api_url, &:formatted_url
49
+
50
+ # The Getting Started checklist in display order. Task names map to
51
+ # frontend copy/components by convention.
52
+ many :setup_tasks,
53
+ resource: proc { Spree.api.admin_setup_task_serializer }
54
+
38
55
  attribute :supported_currencies do |store|
39
56
  store.supported_currencies_list.map(&:iso_code)
40
57
  end
41
58
 
42
59
  attribute :supported_locales, &:supported_locales_list
43
60
 
61
+ # Canonical set of locales a merchant may translate content into,
62
+ # independent of the store's currently-configured locales. Identical
63
+ # for every store, so the locale pickers can offer the full list
64
+ # rather than only locales already in use (avoids a chicken-and-egg
65
+ # where a new locale can never be added). See `Spree::Locales::ALL`.
66
+ attribute :available_locales do
67
+ Spree::Locales::ALL
68
+ end
69
+
44
70
  attribute :logo_url do |store|
45
71
  image_url_for(store.logo)
46
72
  end
@@ -13,13 +13,20 @@ module Spree
13
13
  weight_unit: [:string, nullable: true], dimensions_unit: [:string, nullable: true],
14
14
  available_stock: [:number, nullable: true],
15
15
  reserved_quantity: :number, total_on_hand: [:number, nullable: true],
16
+ preorderable: :boolean, preorder_ships_at: [:string, nullable: true],
17
+ backorder_limit: [:number, nullable: true],
16
18
  deleted_at: [:string, nullable: true],
17
19
  metadata: 'Record<string, unknown>'
18
20
 
19
21
  attributes :metadata, :position, :cost_price, :cost_currency,
20
- :barcode, :weight_unit, :dimensions_unit, deleted_at: :iso8601,
22
+ :barcode, :weight_unit, :dimensions_unit, :backorder_limit,
23
+ preorder_ships_at: :iso8601, deleted_at: :iso8601,
21
24
  created_at: :iso8601, updated_at: :iso8601
22
25
 
26
+ attribute :preorderable do |variant|
27
+ variant.preorderable?
28
+ end
29
+
23
30
  attribute :tax_category_id do |variant|
24
31
  variant.tax_category&.prefixed_id
25
32
  end
@@ -16,6 +16,17 @@ module Spree
16
16
  object.prefixed_id
17
17
  end
18
18
 
19
+ # Declares money/display attributes that render +null+ for gated
20
+ # (prices_hidden) guests, keyed off the same +hide_prices+ flag the
21
+ # +price_for+/+price_in+ helpers use — so the whole price surface
22
+ # (product prices AND cart/order/line-item totals) is gated from one
23
+ # place instead of per-attribute opt-in.
24
+ def self.money_attributes(*names)
25
+ names.each do |name|
26
+ attribute(name) { |object| object.public_send(name) unless params[:hide_prices] }
27
+ end
28
+ end
29
+
19
30
  # Context accessors
20
31
  def current_store
21
32
  params[:store]
@@ -77,6 +88,7 @@ module Spree
77
88
  # Memoized per variant to avoid duplicate queries
78
89
  def price_for(variant, quantity: nil)
79
90
  return nil unless variant.respond_to?(:price_for)
91
+ return nil if params[:hide_prices]
80
92
 
81
93
  @price_for_cache ||= {}
82
94
  cache_key = [variant.id, quantity]
@@ -95,6 +107,7 @@ module Spree
95
107
  # Memoized per variant to avoid duplicate queries
96
108
  def price_in(variant)
97
109
  return nil unless variant.respond_to?(:price_in)
110
+ return nil if params[:hide_prices]
98
111
 
99
112
  @price_in_cache ||= {}
100
113
  return @price_in_cache[variant.id] if @price_in_cache.key?(variant.id)
@@ -8,18 +8,18 @@ module Spree
8
8
  customer_note: [:string, nullable: true], market_id: [:string, nullable: true],
9
9
  currency: :string, locale: [:string, nullable: true], total_quantity: :number,
10
10
  requirements: 'Array<{step: string, field: string, message: string}>',
11
- item_total: :string, display_item_total: :string,
12
- delivery_total: :string, display_delivery_total: :string,
13
- adjustment_total: :string, display_adjustment_total: :string,
14
- discount_total: :string, display_discount_total: :string,
15
- tax_total: :string, display_tax_total: :string,
16
- included_tax_total: :string, display_included_tax_total: :string,
17
- additional_tax_total: :string, display_additional_tax_total: :string,
18
- store_credit_total: :string, display_store_credit_total: :string,
19
- gift_card_total: :string, display_gift_card_total: :string,
11
+ item_total: [:string, nullable: true], display_item_total: [:string, nullable: true],
12
+ delivery_total: [:string, nullable: true], display_delivery_total: [:string, nullable: true],
13
+ adjustment_total: [:string, nullable: true], display_adjustment_total: [:string, nullable: true],
14
+ discount_total: [:string, nullable: true], display_discount_total: [:string, nullable: true],
15
+ tax_total: [:string, nullable: true], display_tax_total: [:string, nullable: true],
16
+ included_tax_total: [:string, nullable: true], display_included_tax_total: [:string, nullable: true],
17
+ additional_tax_total: [:string, nullable: true], display_additional_tax_total: [:string, nullable: true],
18
+ store_credit_total: [:string, nullable: true], display_store_credit_total: [:string, nullable: true],
19
+ gift_card_total: [:string, nullable: true], display_gift_card_total: [:string, nullable: true],
20
20
  covered_by_store_credit: :boolean,
21
- total: :string, display_total: :string,
22
- amount_due: :string, display_amount_due: :string,
21
+ total: [:string, nullable: true], display_total: [:string, nullable: true],
22
+ amount_due: [:string, nullable: true], display_amount_due: [:string, nullable: true],
23
23
  shipping_eq_billing_address: :boolean,
24
24
  warnings: 'Array<{code: string, message: string, line_item_id?: string, variant_id?: string}>',
25
25
  billing_address: { nullable: true }, shipping_address: { nullable: true },
@@ -35,22 +35,25 @@ module Spree
35
35
  end
36
36
 
37
37
  attributes :number, :token, :email, :customer_note,
38
- :currency, :locale, :total_quantity,
39
- :item_total, :display_item_total,
40
- :adjustment_total, :display_adjustment_total,
41
- :discount_total, :display_discount_total,
42
- :tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
43
- :additional_tax_total, :display_additional_tax_total, :total, :display_total,
44
- :gift_card_total, :display_gift_card_total,
45
- :amount_due, :display_amount_due,
46
- :delivery_total, :display_delivery_total, :warnings
38
+ :currency, :locale, :total_quantity, :warnings
39
+
40
+ # Nulled for gated (prices_hidden) guests so the cart can't leak the
41
+ # prices that product/variant serializers already withhold.
42
+ money_attributes :item_total, :display_item_total,
43
+ :adjustment_total, :display_adjustment_total,
44
+ :discount_total, :display_discount_total,
45
+ :tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
46
+ :additional_tax_total, :display_additional_tax_total, :total, :display_total,
47
+ :gift_card_total, :display_gift_card_total,
48
+ :amount_due, :display_amount_due,
49
+ :delivery_total, :display_delivery_total
47
50
 
48
51
  attribute :store_credit_total do |order|
49
- order.total_applied_store_credit.to_s
52
+ order.total_applied_store_credit.to_s unless params[:hide_prices]
50
53
  end
51
54
 
52
55
  attribute :display_store_credit_total do |order|
53
- order.display_total_applied_store_credit.to_s
56
+ order.display_total_applied_store_credit.to_s unless params[:hide_prices]
54
57
  end
55
58
 
56
59
  attribute :covered_by_store_credit do |order|
@@ -5,13 +5,17 @@ module Spree
5
5
  # Replaces CartPromotionSerializer and OrderPromotionSerializer.
6
6
  class DiscountSerializer < BaseSerializer
7
7
  typelize name: :string, description: [:string, nullable: true], code: [:string, nullable: true],
8
- amount: :string, display_amount: :string, promotion_id: :string
8
+ amount: [:string, nullable: true], display_amount: [:string, nullable: true], promotion_id: :string
9
9
 
10
10
  attribute :promotion_id do |record|
11
11
  record.promotion&.prefixed_id
12
12
  end
13
13
 
14
- attributes :name, :description, :code, :amount, :display_amount
14
+ attributes :name, :description, :code
15
+
16
+ # Nulled for gated (prices_hidden) guests so an applied discount can't
17
+ # leak the amount the cart/order totals already withhold.
18
+ money_attributes :amount, :display_amount
15
19
  end
16
20
  end
17
21
  end
@@ -5,21 +5,24 @@ module Spree
5
5
  typelize number: :string, status: :string, fulfillment_type: :string,
6
6
  tracking: [:string, nullable: true],
7
7
  tracking_url: [:string, nullable: true], fulfilled_at: [:string, nullable: true],
8
- cost: :string, display_cost: :string,
9
- total: :string, display_total: :string,
10
- discount_total: :string, display_discount_total: :string,
11
- additional_tax_total: :string, display_additional_tax_total: :string,
12
- included_tax_total: :string, display_included_tax_total: :string,
13
- tax_total: :string, display_tax_total: :string,
8
+ cost: [:string, nullable: true], display_cost: [:string, nullable: true],
9
+ total: [:string, nullable: true], display_total: [:string, nullable: true],
10
+ discount_total: [:string, nullable: true], display_discount_total: [:string, nullable: true],
11
+ additional_tax_total: [:string, nullable: true], display_additional_tax_total: [:string, nullable: true],
12
+ included_tax_total: [:string, nullable: true], display_included_tax_total: [:string, nullable: true],
13
+ tax_total: [:string, nullable: true], display_tax_total: [:string, nullable: true],
14
14
  items: 'Array<{ item_id: string; variant_id: string; quantity: number }>'
15
15
 
16
- attributes :number, :tracking, :tracking_url,
17
- :cost, :display_cost,
18
- :total, :display_total,
19
- :discount_total, :display_discount_total,
20
- :additional_tax_total, :display_additional_tax_total,
21
- :included_tax_total, :display_included_tax_total,
22
- :tax_total, :display_tax_total
16
+ attributes :number, :tracking, :tracking_url
17
+
18
+ # Nulled for gated (prices_hidden) guests so a fulfillment can't leak the
19
+ # shipping/tax amounts the cart/order totals already withhold.
20
+ money_attributes :cost, :display_cost,
21
+ :total, :display_total,
22
+ :discount_total, :display_discount_total,
23
+ :additional_tax_total, :display_additional_tax_total,
24
+ :included_tax_total, :display_included_tax_total,
25
+ :tax_total, :display_tax_total
23
26
 
24
27
  attribute :status do |shipment|
25
28
  shipment.state
@@ -4,13 +4,13 @@ module Spree
4
4
  class GiftCardSerializer < BaseSerializer
5
5
  typelize code: :string,
6
6
  status: :string,
7
- amount: :string,
8
- amount_used: :string,
9
- amount_authorized: :string,
10
- amount_remaining: :string,
11
- display_amount: :string,
12
- display_amount_used: :string,
13
- display_amount_remaining: :string,
7
+ amount: [:string, nullable: true],
8
+ amount_used: [:string, nullable: true],
9
+ amount_authorized: [:string, nullable: true],
10
+ amount_remaining: [:string, nullable: true],
11
+ display_amount: [:string, nullable: true],
12
+ display_amount_used: [:string, nullable: true],
13
+ display_amount_remaining: [:string, nullable: true],
14
14
  currency: :string,
15
15
  expires_at: [:string, nullable: true],
16
16
  redeemed_at: [:string, nullable: true],
@@ -25,18 +25,22 @@ module Spree
25
25
  gift_card.display_state
26
26
  end
27
27
 
28
- attributes :currency, :amount, :amount_used, :amount_authorized, :amount_remaining
28
+ attributes :currency
29
+
30
+ # Nulled for gated (prices_hidden) guests so a gift card applied to a
31
+ # cart can't leak balances the cart/order totals already withhold.
32
+ money_attributes :amount, :amount_used, :amount_authorized, :amount_remaining
29
33
 
30
34
  attribute :display_amount do |gift_card|
31
- gift_card.display_amount.to_s
35
+ gift_card.display_amount.to_s unless params[:hide_prices]
32
36
  end
33
37
 
34
38
  attribute :display_amount_used do |gift_card|
35
- gift_card.display_amount_used.to_s
39
+ gift_card.display_amount_used.to_s unless params[:hide_prices]
36
40
  end
37
41
 
38
42
  attribute :display_amount_remaining do |gift_card|
39
- gift_card.display_amount_remaining.to_s
43
+ gift_card.display_amount_remaining.to_s unless params[:hide_prices]
40
44
  end
41
45
 
42
46
  attribute :expires_at do |gift_card|
@@ -5,7 +5,7 @@ module Spree
5
5
  module V3
6
6
  class ImportRowSerializer < BaseSerializer
7
7
  typelize import_id: [:string, nullable: true], row_number: :number,
8
- status: :string, validation_errors: 'unknown',
8
+ status: :string, validation_errors: [:string, nullable: true],
9
9
  item_type: [:string, nullable: true], item_id: [:string, nullable: true]
10
10
 
11
11
  attributes :row_number, :status, :validation_errors, :item_type,