spree_api 6.0.0.beta2 → 6.0.0.beta3

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v3/admin/embed_permissions.rb +55 -0
  3. data/app/controllers/concerns/spree/api/v3/admin/role_grant_guard.rb +34 -5
  4. data/app/controllers/concerns/spree/api/v3/current_password_confirmation.rb +8 -0
  5. data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +15 -2
  6. data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +14 -0
  7. data/app/controllers/concerns/spree/api/v3/store/storefront_products.rb +35 -0
  8. data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +9 -0
  9. data/app/controllers/spree/api/v3/admin/base_controller.rb +1 -0
  10. data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +8 -3
  11. data/app/controllers/spree/api/v3/admin/customers_controller.rb +0 -14
  12. data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +29 -2
  13. data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +4 -1
  14. data/app/controllers/spree/api/v3/admin/invitations/acceptance_links_controller.rb +41 -0
  15. data/app/controllers/spree/api/v3/admin/me_controller.rb +11 -0
  16. data/app/controllers/spree/api/v3/admin/orders/claims_controller.rb +14 -1
  17. data/app/controllers/spree/api/v3/admin/orders/exchanges_controller.rb +22 -1
  18. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +1 -1
  19. data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +22 -1
  20. data/app/controllers/spree/api/v3/admin/orders_controller.rb +27 -1
  21. data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +31 -11
  22. data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +1 -19
  23. data/app/controllers/spree/api/v3/admin/prices_controller.rb +33 -2
  24. data/app/controllers/spree/api/v3/admin/products_controller.rb +1 -1
  25. data/app/controllers/spree/api/v3/admin/resource_controller.rb +1 -0
  26. data/app/controllers/spree/api/v3/admin/sellers/invitations/acceptance_links_controller.rb +37 -0
  27. data/app/controllers/spree/api/v3/admin/setup_controller.rb +1 -1
  28. data/app/controllers/spree/api/v3/admin/store_controller.rb +4 -0
  29. data/app/controllers/spree/api/v3/admin/translations/batches_controller.rb +1 -1
  30. data/app/controllers/spree/api/v3/admin/translations_controller.rb +1 -1
  31. data/app/controllers/spree/api/v3/admin/webhook_deliveries_controller.rb +12 -0
  32. data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +27 -0
  33. data/app/controllers/spree/api/v3/base_controller.rb +7 -3
  34. data/app/controllers/spree/api/v3/resource_controller.rb +11 -1
  35. data/app/controllers/spree/api/v3/seller/auth_controller.rb +1 -1
  36. data/app/controllers/spree/api/v3/seller/invitation_acceptances_controller.rb +5 -2
  37. data/app/controllers/spree/api/v3/seller/invitations/acceptance_links_controller.rb +34 -0
  38. data/app/controllers/spree/api/v3/seller/me_controller.rb +25 -2
  39. data/app/controllers/spree/api/v3/seller/orders_controller.rb +19 -24
  40. data/app/controllers/spree/api/v3/seller/password_resets_controller.rb +1 -1
  41. data/app/controllers/spree/api/v3/seller/resource_controller.rb +4 -0
  42. data/app/controllers/spree/api/v3/store/carts/items_controller.rb +7 -4
  43. data/app/controllers/spree/api/v3/store/carts/payment_sessions_controller.rb +10 -1
  44. data/app/controllers/spree/api/v3/store/carts/payments_controller.rb +5 -1
  45. data/app/controllers/spree/api/v3/store/companies/members_controller.rb +10 -11
  46. data/app/controllers/spree/api/v3/store/companies/orders_controller.rb +6 -0
  47. data/app/controllers/spree/api/v3/store/products/filters_controller.rb +1 -0
  48. data/app/controllers/spree/api/v3/store/products_controller.rb +6 -12
  49. data/app/controllers/spree/api/v3/store/resource_controller.rb +4 -0
  50. data/app/controllers/spree/api/v3/store/wishlist_items_controller.rb +11 -0
  51. data/app/controllers/spree/api/v3/webhooks/fulfillments_controller.rb +10 -6
  52. data/app/controllers/spree/api/v3/webhooks/payments_controller.rb +6 -2
  53. data/app/controllers/spree/api/v3/webhooks/payouts_controller.rb +6 -2
  54. data/app/jobs/spree/webhook_delivery_job.rb +18 -4
  55. data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +6 -0
  56. data/app/serializers/spree/api/v3/admin/invitation_acceptance_link_serializer.rb +15 -0
  57. data/app/serializers/spree/api/v3/admin/invitation_serializer.rb +0 -11
  58. data/app/serializers/spree/api/v3/admin/order_group_serializer.rb +7 -0
  59. data/app/serializers/spree/api/v3/admin/store_serializer.rb +8 -0
  60. data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +6 -2
  61. data/app/serializers/spree/api/v3/gift_card_serializer.rb +3 -1
  62. data/app/serializers/spree/api/v3/line_item_serializer.rb +2 -1
  63. data/app/serializers/spree/api/v3/market_serializer.rb +5 -0
  64. data/app/serializers/spree/api/v3/product_filters_serializer.rb +3 -0
  65. data/app/serializers/spree/api/v3/seller/account_serializer.rb +19 -0
  66. data/app/serializers/spree/api/v3/seller/invitation_acceptance_link_serializer.rb +15 -0
  67. data/app/serializers/spree/api/v3/seller/invitation_serializer.rb +1 -10
  68. data/app/services/spree/api/v3/filters_aggregator.rb +5 -2
  69. data/config/routes.rb +7 -0
  70. data/lib/spree/api/dependencies.rb +3 -0
  71. data/lib/spree/api/openapi/schema_helper.rb +10 -4
  72. metadata +14 -6
@@ -5,6 +5,7 @@ module Spree
5
5
  class ProductsController < ResourceController
6
6
  include Spree::Api::V3::HttpCaching
7
7
  include Spree::Api::V3::Store::SearchProviderSupport
8
+ include Spree::Api::V3::Store::StorefrontProducts
8
9
 
9
10
  protected
10
11
 
@@ -28,19 +29,12 @@ module Spree
28
29
  end
29
30
  end
30
31
 
32
+ # Catalog narrowing for the buyer: their company's effective
33
+ # catalogs, their group's, or the channel default — union of
34
+ # assortments, resolved in one place
35
+ # (docs/plans/6.0-b2b-companies-and-catalogs.md).
31
36
  def scope
32
- base = super.available(Time.current, Spree::Current.currency, include_preorderable: true)
33
-
34
- # Catalog narrowing for the buyer: their company's effective
35
- # catalogs, their group's, or the channel default — union of
36
- # assortments, resolved in one place
37
- # (docs/plans/6.0-b2b-companies-and-catalogs.md).
38
- Spree.products_for_context_service.call(
39
- store: current_store,
40
- channel: current_channel,
41
- customer: current_user,
42
- base: base
43
- ).value
37
+ storefront_products(super)
44
38
  end
45
39
 
46
40
  # these scopes are not automatically picked by ar_lazy_preload gem and we need to explicitly include them
@@ -28,6 +28,10 @@ module Spree
28
28
  def authenticate_request!
29
29
  authenticate_api_key!
30
30
  end
31
+
32
+ def ransack_auth_object
33
+ :store
34
+ end
31
35
  end
32
36
  end
33
37
  end
@@ -3,6 +3,8 @@ module Spree
3
3
  module V3
4
4
  module Store
5
5
  class WishlistItemsController < ResourceController
6
+ include Spree::Api::V3::Store::StorefrontProducts
7
+
6
8
  prepend_before_action :require_authentication!
7
9
 
8
10
  protected
@@ -29,6 +31,15 @@ module Spree
29
31
  def resource_permitted_attributes
30
32
  [:variant_id, :quantity]
31
33
  end
34
+
35
+ # The item renders its variant and product, so the variant must be
36
+ # one the buyer could find in the listing — not a draft, another
37
+ # catalog's product, or an id guessed from the sequence.
38
+ def permitted_params
39
+ @permitted_params ||= super.tap do |attributes|
40
+ attributes[:variant_id] = storefront_variants.find_by_prefix_id!(params[:variant_id]).id if params.key?(:variant_id)
41
+ end
42
+ end
32
43
  end
33
44
  end
34
45
  end
@@ -15,7 +15,6 @@ module Spree
15
15
  # sender's timeout.
16
16
  class FulfillmentsController < ActionController::API
17
17
  include ActionController::RateLimiting
18
- include Spree::Core::ControllerHelpers::Store
19
18
 
20
19
  # Must render — instance_exec'd in a before_action, where only
21
20
  # render/redirect halts the chain.
@@ -31,12 +30,17 @@ module Spree
31
30
  with: RATE_LIMIT_RESPONSE
32
31
 
33
32
  def create
34
- integration = current_store.integrations.active.find_by_prefix_id!(params[:integration_id])
33
+ # By id alone: providers call back without an API key or store
34
+ # header, so the request cannot name the store — the record does.
35
+ # The signature check below, against the record's own secret, is
36
+ # what authenticates the request.
37
+ integration = Spree::Integration.active.find_by_prefix_id!(params[:integration_id])
38
+ Spree::Current.store = integration.store
35
39
 
36
40
  event = integration.parse_webhook_event(request.raw_post, request.headers)
37
41
  return head :ok if event.nil?
38
42
 
39
- delivery = find_delivery(event[:tracking_code])
43
+ delivery = find_delivery(integration.store, event[:tracking_code])
40
44
  return head :ok if delivery.nil?
41
45
 
42
46
  Spree.delivery_update_tracking_workflow.call(
@@ -57,7 +61,7 @@ module Spree
57
61
  private
58
62
 
59
63
  # Scoped to the store the verified integration belongs to — a
60
- # tracking code can never address another tenant's parcel — and to
64
+ # tracking code can never address another store's parcel — and to
61
65
  # consignments whose parcel was not canceled, so a recycled code
62
66
  # cannot reopen an old one. The newest wins when a code was reused.
63
67
  # Carriers reuse tracking numbers across years, so a code can match
@@ -67,14 +71,14 @@ module Spree
67
71
  # database. The newest survivor wins.
68
72
  CANDIDATE_LIMIT = 20
69
73
 
70
- def find_delivery(tracking_code)
74
+ def find_delivery(store, tracking_code)
71
75
  return if tracking_code.blank?
72
76
 
73
77
  # Paged rather than capped: a tracking number reused across
74
78
  # canceled owners would otherwise fill the window and hide the
75
79
  # live delivery behind them, and the webhook would be
76
80
  # acknowledged without applying the carrier's update.
77
- current_store.deliveries.
81
+ store.deliveries.
78
82
  where(tracking_number: tracking_code).
79
83
  order(created_at: :desc, id: :desc).
80
84
  includes(:owner).
@@ -4,7 +4,6 @@ module Spree
4
4
  module Webhooks
5
5
  class PaymentsController < ActionController::API
6
6
  include ActionController::RateLimiting
7
- include Spree::Core::ControllerHelpers::Store
8
7
 
9
8
  # Must render — instance_exec'd in a before_action, where only
10
9
  # render/redirect halts the chain.
@@ -24,7 +23,12 @@ module Spree
24
23
  # Verifies the webhook signature synchronously (returns 401 if invalid),
25
24
  # then enqueues async processing and returns 200 immediately.
26
25
  def create
27
- payment_method = current_store.payment_methods.find_by_prefix_id!(params[:payment_method_id])
26
+ # By id alone: providers call back without an API key or store
27
+ # header, so the request cannot name the store — the record does.
28
+ # The signature check below, against the record's own secret, is
29
+ # what authenticates the request.
30
+ payment_method = Spree::PaymentMethod.find_by_prefix_id!(params[:payment_method_id])
31
+ Spree::Current.store = payment_method.store
28
32
 
29
33
  # Signature verification must be synchronous — invalid = 401
30
34
  result = payment_method.parse_webhook_event(request.raw_post, request.headers)
@@ -19,7 +19,6 @@ module Spree
19
19
  # database writes, well inside any sender's timeout.
20
20
  class PayoutsController < ActionController::API
21
21
  include ActionController::RateLimiting
22
- include Spree::Core::ControllerHelpers::Store
23
22
 
24
23
  # Must render — instance_exec'd in a before_action, where only
25
24
  # render/redirect halts the chain.
@@ -35,7 +34,12 @@ module Spree
35
34
  with: RATE_LIMIT_RESPONSE
36
35
 
37
36
  def create
38
- payment_method = current_store.payment_methods.find_by_prefix_id!(params[:payment_method_id])
37
+ # By id alone: providers call back without an API key or store
38
+ # header, so the request cannot name the store — the record does.
39
+ # The signature check below, against the record's own secret, is
40
+ # what authenticates the request.
41
+ payment_method = Spree::PaymentMethod.find_by_prefix_id!(params[:payment_method_id])
42
+ Spree::Current.store = payment_method.store
39
43
  return head :not_found unless payment_method.respond_to?(:handle_payout_webhook)
40
44
 
41
45
  payment_method.handle_payout_webhook(request.raw_post, request.headers)
@@ -2,11 +2,19 @@
2
2
 
3
3
  module Spree
4
4
  class WebhookDeliveryJob < Spree::BaseJob
5
+ # Raised when the endpoint answered with anything other than 2xx, timed
6
+ # out or could not be reached, so the delivery is sent again with backoff.
7
+ class DeliveryFailed < StandardError; end
8
+
5
9
  queue_as Spree.queues.webhooks
6
10
 
7
- # Webhook delivery hits external endpoints; broad retry covers network timeouts,
8
- # 5xx, DNS failures, etc.
11
+ # Delivery is retry-safe: each attempt overwrites the outcome on the same
12
+ # delivery record, and receivers deduplicate on the event id.
9
13
  retry_on StandardError, wait: :polynomially_longer, attempts: 5
14
+ # The outcome of the last attempt is already on the delivery record, so an
15
+ # exhausted retry is not re-raised into the queue backend.
16
+ retry_on DeliveryFailed, wait: :polynomially_longer, attempts: 5 do |_job, _error|
17
+ end
10
18
  # Must come after `retry_on StandardError` so DeserializationError lands in discard
11
19
  # (ActiveJob handler lookup is reverse-declaration-order).
12
20
  discard_on ActiveJob::DeserializationError
@@ -21,9 +29,15 @@ module Spree
21
29
  delivery = Spree::WebhookDelivery.find_by(id: delivery_id)
22
30
  return if delivery.nil?
23
31
 
32
+ endpoint = delivery.webhook_endpoint
33
+ return if endpoint.nil?
34
+ # A retry stops once the endpoint was switched off, by hand or by auto-disable.
35
+ return if executions > 1 && !endpoint.active?
36
+
24
37
  delivery.payload_secrets = payload_secrets
25
- secret_key = delivery.webhook_endpoint.secret_key
26
- Spree::Webhooks::DeliverWebhook.call(delivery: delivery, secret_key: secret_key)
38
+ Spree::Webhooks::DeliverWebhook.call(delivery: delivery, secret_key: endpoint.secret_key)
39
+
40
+ raise DeliveryFailed, "Webhook delivery #{delivery.prefixed_id} failed" if delivery.failed?
27
41
  end
28
42
  end
29
43
  end
@@ -18,6 +18,12 @@ module Spree
18
18
 
19
19
  attributes created_at: :iso8601, updated_at: :iso8601
20
20
 
21
+ # A bearer credential: in full only to a caller who may read gift
22
+ # cards, masked when the card rides along on an order.
23
+ attribute :code do |gift_card|
24
+ params[:gift_card_codes] ? gift_card.display_code : gift_card.masked_code
25
+ end
26
+
21
27
  attribute :customer_id do |gift_card|
22
28
  gift_card.customer&.prefixed_id
23
29
  end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # The acceptance link of one staff invitation, served on its own so the
6
+ # token never travels with the invitation listing.
7
+ class InvitationAcceptanceLinkSerializer < V3::BaseSerializer
8
+ typelize acceptance_url: :string
9
+
10
+ attributes :acceptance_url
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -13,7 +13,6 @@ module Spree
13
13
  role_name: :string,
14
14
  inviter_email: :string,
15
15
  expires_at: :string,
16
- acceptance_url: :string,
17
16
  invitee_exists: :boolean,
18
17
  store: '{ id: string; name: string }'
19
18
 
@@ -34,16 +33,6 @@ module Spree
34
33
  invitation.inviter.email
35
34
  end
36
35
 
37
- # Absolute URL when a dashboard origin is configured, otherwise the
38
- # path so the SPA can prepend `window.location.origin`.
39
- attribute :acceptance_url do |invitation|
40
- if Spree::Config[:dashboard_url].present?
41
- Rails.application.routes.url_helpers.admin_invitation_acceptance_url(invitation)
42
- else
43
- "/accept-invitation/#{invitation.prefixed_id}?token=#{invitation.token}"
44
- end
45
- end
46
-
47
36
  # Drives the SPA's sign-in vs sign-up branch on the acceptance page.
48
37
  # Looked up by email so an admin who's already on another store sees
49
38
  # the password prompt, not a fresh account form.
@@ -13,6 +13,8 @@ module Spree
13
13
  cart_id: 'string | null',
14
14
  seller_count: :number,
15
15
  includes_first_party: :boolean,
16
+ confirmation_delivered: :boolean,
17
+ store_owner_notification_delivered: :boolean,
16
18
  created_at: :string, updated_at: :string
17
19
 
18
20
  attribute :customer_id do |group|
@@ -34,6 +36,11 @@ module Spree
34
36
  group.includes_first_party?
35
37
  end
36
38
 
39
+ # A split checkout is confirmed once, from here — every child order
40
+ # places silently, so their own flags are false and only the group
41
+ # can answer whether the customer was told.
42
+ attributes :confirmation_delivered, :store_owner_notification_delivered
43
+
37
44
  attributes :created_at, :updated_at
38
45
 
39
46
  # Re-declared against the admin serializers. Inheriting the store
@@ -42,6 +42,10 @@ module Spree
42
42
  preferred_payout_provider: 'string | null',
43
43
  preferred_default_payouts_schedule_interval: :string,
44
44
  preferred_default_minimum_payout_amount: :number,
45
+ preferred_auto_approve_sellers: :boolean,
46
+ preferred_auto_approve_seller_products: :boolean,
47
+ preferred_send_seller_transactional_emails: :boolean,
48
+ preferred_default_commission_tax_rate: :number,
45
49
  preferred_document_number_format: :string,
46
50
  preferred_order_number_prefix: :string,
47
51
  preferred_order_number_suffix: :string,
@@ -90,6 +94,10 @@ module Spree
90
94
  :preferred_payout_provider,
91
95
  :preferred_default_payouts_schedule_interval,
92
96
  :preferred_default_minimum_payout_amount,
97
+ :preferred_auto_approve_sellers,
98
+ :preferred_auto_approve_seller_products,
99
+ :preferred_send_seller_transactional_emails,
100
+ :preferred_default_commission_tax_rate,
93
101
  :preferred_document_number_format,
94
102
  :preferred_order_number_prefix,
95
103
  :preferred_order_number_suffix,
@@ -19,7 +19,7 @@ module Spree
19
19
  response_body: [:string, nullable: true],
20
20
  success: [:boolean, nullable: true],
21
21
  delivered_at: [:string, nullable: true],
22
- payload: 'Record<string, unknown>',
22
+ payload: ['Record<string, unknown>', nullable: true],
23
23
  webhook_endpoint_id: :string,
24
24
  webhook_endpoint_url: :string
25
25
 
@@ -29,8 +29,12 @@ module Spree
29
29
  delivered_at: :iso8601
30
30
 
31
31
  # Redacted again on read: deliveries written before payload redaction
32
- # shipped still hold live credentials in the column.
32
+ # shipped still hold live credentials in the column. Null when the
33
+ # caller cannot read the record the event is about.
33
34
  attribute :payload do |delivery|
35
+ visible = params[:payload_visible]
36
+ next nil if visible && !visible.call(delivery)
37
+
34
38
  Spree::WebhookPayloadRedaction.split(delivery.payload).first
35
39
  end
36
40
 
@@ -17,8 +17,10 @@ module Spree
17
17
  expired: :boolean,
18
18
  active: :boolean
19
19
 
20
+ # A bearer credential: masked when the order is shown to someone other
21
+ # than its buyer (a company's other members).
20
22
  attribute :code do |gift_card|
21
- gift_card.display_code
23
+ params[:hide_credentials] ? gift_card.masked_code : gift_card.display_code
22
24
  end
23
25
 
24
26
  attribute :status do |gift_card|
@@ -71,7 +71,8 @@ module Spree
71
71
  if: proc { expand?('seller') }
72
72
 
73
73
  many :option_values, resource: proc { Spree.api.option_value_serializer }
74
- many :digital_links, resource: proc { Spree.api.digital_link_serializer }
74
+ # Download links carry a bearer token; only the buyer gets them.
75
+ many :digital_links, resource: proc { Spree.api.digital_link_serializer }, if: proc { !params[:hide_credentials] }
75
76
  # Tax hangs off its adjustable, never off the cart or order: the
76
77
  # exactly-one-adjustable rule makes the nested view complete, and the
77
78
  # owner already carries the tax totals.
@@ -6,11 +6,16 @@ module Spree
6
6
  default_locale: :string,
7
7
  supported_locales: [:string, multi: true],
8
8
  country_codes: [:string, multi: true],
9
+ country_isos: [:string, multi: true],
9
10
  tax_inclusive: :boolean,
10
11
  default: :boolean
11
12
 
12
13
  attributes :name, :currency, :default_locale, :tax_inclusive, :default, :country_codes
13
14
 
15
+ # @deprecated Storefronts shipped against this name; use +country_codes+.
16
+ # Removed in 6.1.
17
+ attribute :country_isos, &:country_codes
18
+
14
19
  attribute :supported_locales do |market|
15
20
  market.supported_locales_list
16
21
  end
@@ -12,6 +12,9 @@ module Spree
12
12
  attribute :filters do |result|
13
13
  result.filters.filter_map do |filter|
14
14
  filter_type = filter[:type]
15
+ # A price range is prices; a gated guest sees none.
16
+ next if filter_type == 'price_range' && params[:hide_prices]
17
+
15
18
  serializer_class = case filter_type
16
19
  when 'price_range'
17
20
  Spree.api.product_filter_price_range_serializer
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Seller
5
+ # The signed-in person's own account, as `/me` answers it.
6
+ #
7
+ # Extends the team member shape rather than widening it: `selected_locale`
8
+ # is a preference its owner sets, and the team list serializes colleagues
9
+ # with the parent — where publishing each member's chosen language would
10
+ # tell the whole team something none of them asked to share.
11
+ class AccountSerializer < Seller::TeamMemberSerializer
12
+ typelize selected_locale: [:string, nullable: true]
13
+
14
+ attributes :selected_locale
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Seller
5
+ # The acceptance link of one seller-team invitation, served on its own
6
+ # so the token never travels with the invitation listing.
7
+ class InvitationAcceptanceLinkSerializer < V3::BaseSerializer
8
+ typelize acceptance_url: :string
9
+
10
+ attributes :acceptance_url
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,8 +5,7 @@ module Spree
5
5
  class InvitationSerializer < V3::BaseSerializer
6
6
  typelize email: :string, status: [:string, enum: Spree::Invitation.statuses, enum_type_name: 'InvitationStatus'],
7
7
  expires_at: [:string, nullable: true],
8
- accepted_at: [:string, nullable: true],
9
- acceptance_url: :string
8
+ accepted_at: [:string, nullable: true]
10
9
 
11
10
  attributes :email,
12
11
  created_at: :iso8601, expires_at: :iso8601, accepted_at: :iso8601
@@ -14,14 +13,6 @@ module Spree
14
13
  attribute :status do |invitation|
15
14
  invitation.status.to_s
16
15
  end
17
-
18
- attribute :acceptance_url do |invitation|
19
- if Spree::Config[:seller_panel_url].present? || Spree::Config[:dashboard_url].present?
20
- Rails.application.routes.url_helpers.admin_invitation_acceptance_url(invitation)
21
- else
22
- "/accept-invitation/#{invitation.prefixed_id}?token=#{invitation.token}"
23
- end
24
- end
25
16
  end
26
17
  end
27
18
  end
@@ -51,8 +51,11 @@ module Spree
51
51
 
52
52
 
53
53
  def price_filter
54
- # Remove ordering to avoid PostgreSQL DISTINCT + ORDER BY conflicts
55
- prices = Spree::Price.for_products(@scope.reorder(''), @currency)
54
+ # Remove ordering to avoid PostgreSQL DISTINCT + ORDER BY conflicts.
55
+ # Base prices only: a price list's rows are for its own audience
56
+ # (a company's contract, a draft or scheduled list), and the range is
57
+ # shown to everyone.
58
+ prices = Spree::Price.base_prices.for_products(@scope.reorder(''), @currency)
56
59
  min = prices.minimum(:amount)
57
60
  max = prices.maximum(:amount)
58
61
  return nil if min.nil? || max.nil?
data/config/routes.rb CHANGED
@@ -273,6 +273,7 @@ Spree::Core::Engine.add_routes do
273
273
  member do
274
274
  patch :resend
275
275
  end
276
+ resource :acceptance_link, only: [:show], controller: 'invitations/acceptance_links'
276
277
  end
277
278
  resources :api_keys, only: [:index, :show, :create, :update, :destroy] do
278
279
  collection do
@@ -590,6 +591,7 @@ Spree::Core::Engine.add_routes do
590
591
  member do
591
592
  patch :resend
592
593
  end
594
+ resource :acceptance_link, only: [:show], controller: 'sellers/invitations/acceptance_links'
593
595
  end
594
596
 
595
597
  # What this seller submitted about the requirements, and the
@@ -875,6 +877,10 @@ Spree::Core::Engine.add_routes do
875
877
  patch 'auth/password_resets/:id', to: 'password_resets#update'
876
878
 
877
879
  get 'me', to: 'me#show'
880
+ # Self-service edits to the signed-in person's own account — their
881
+ # name, photo and panel language. Distinct from `profile`, which is
882
+ # the seller business they act for.
883
+ patch 'me', to: 'me#update'
878
884
 
879
885
  # Singular: the seller in play is always `current_seller`.
880
886
  resource :profile, only: [:show, :update], controller: 'profile'
@@ -896,6 +902,7 @@ Spree::Core::Engine.add_routes do
896
902
  member do
897
903
  patch :resend
898
904
  end
905
+ resource :acceptance_link, only: [:show], controller: 'invitations/acceptance_links'
899
906
  end
900
907
 
901
908
  resources :products, only: [:index, :show, :create, :update, :destroy] do
@@ -187,7 +187,9 @@ module Spree
187
187
  seller_profile_serializer: 'Spree::Api::V3::Seller::ProfileSerializer',
188
188
  seller_policy_serializer: 'Spree::Api::V3::Seller::PolicySerializer',
189
189
  seller_team_member_serializer: 'Spree::Api::V3::Seller::TeamMemberSerializer',
190
+ seller_account_serializer: 'Spree::Api::V3::Seller::AccountSerializer',
190
191
  seller_invitation_serializer: 'Spree::Api::V3::Seller::InvitationSerializer',
192
+ seller_invitation_acceptance_link_serializer: 'Spree::Api::V3::Seller::InvitationAcceptanceLinkSerializer',
191
193
  seller_product_serializer: 'Spree::Api::V3::Seller::ProductSerializer',
192
194
  seller_product_type_serializer: 'Spree::Api::V3::Seller::ProductTypeSerializer',
193
195
  seller_delivery_profile_serializer: 'Spree::Api::V3::Seller::DeliveryProfileSerializer',
@@ -267,6 +269,7 @@ module Spree
267
269
  admin_webhook_endpoint_serializer: 'Spree::Api::V3::Admin::WebhookEndpointSerializer',
268
270
  admin_webhook_delivery_serializer: 'Spree::Api::V3::Admin::WebhookDeliverySerializer',
269
271
  admin_invitation_serializer: 'Spree::Api::V3::Admin::InvitationSerializer',
272
+ admin_invitation_acceptance_link_serializer: 'Spree::Api::V3::Admin::InvitationAcceptanceLinkSerializer',
270
273
  admin_role_serializer: 'Spree::Api::V3::Admin::RoleSerializer',
271
274
  admin_permission_serializer: 'Spree::Api::V3::Admin::PermissionSerializer',
272
275
  admin_export_serializer: 'Spree::Api::V3::Admin::ExportSerializer',
@@ -250,8 +250,8 @@ module Spree
250
250
  #
251
251
  # The seller branch has no API-key credential, so its AuthResponse is
252
252
  # neither the store's nor the admin's: signing in returns the JWT, the
253
- # team member who signed in, and the sellers they may act for — the
254
- # panel cannot make another request until it has picked one.
253
+ # person who signed in, and the sellers they may act for — the panel
254
+ # cannot make another request until it has picked one.
255
255
  def seller_schemas
256
256
  schemas = common_schemas
257
257
 
@@ -259,7 +259,10 @@ module Spree
259
259
  type: :object,
260
260
  properties: {
261
261
  token: { type: :string, description: 'JWT access token, `seller_api` audience' },
262
- user: { '$ref' => '#/components/schemas/TeamMember' },
262
+ # `Account`, not `TeamMember`: the panel adopts the signed-in
263
+ # person's saved language from this payload, which is what
264
+ # carries the choice to a second browser.
265
+ user: { '$ref' => '#/components/schemas/Account' },
263
266
  sellers: {
264
267
  type: :array,
265
268
  description: 'The sellers this user may act for. Pick one and send its `id` as `X-Spree-Seller-Id` on every subsequent request.',
@@ -309,7 +312,10 @@ module Spree
309
312
  type: :object,
310
313
  description: 'The signed-in user, the sellers they may act for, and what they may do on the selected one',
311
314
  properties: {
312
- user: { '$ref' => '#/components/schemas/TeamMember' },
315
+ # `Account`, not `TeamMember`: the person reading their own
316
+ # record also sees the panel language they chose, which the
317
+ # team list does not publish about a colleague.
318
+ user: { '$ref' => '#/components/schemas/Account' },
313
319
  sellers: { type: :array, items: { '$ref' => '#/components/schemas/SellerMeSummary' } },
314
320
  permissions: { type: :array, items: { '$ref' => '#/components/schemas/PermissionRule' } },
315
321
  permission_keys: {