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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/spree/api/v3/admin/embed_permissions.rb +55 -0
- data/app/controllers/concerns/spree/api/v3/admin/role_grant_guard.rb +34 -5
- data/app/controllers/concerns/spree/api/v3/current_password_confirmation.rb +8 -0
- data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +15 -2
- data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +14 -0
- data/app/controllers/concerns/spree/api/v3/store/storefront_products.rb +35 -0
- data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +9 -0
- data/app/controllers/spree/api/v3/admin/base_controller.rb +1 -0
- data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +8 -3
- data/app/controllers/spree/api/v3/admin/customers_controller.rb +0 -14
- data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +29 -2
- data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +4 -1
- data/app/controllers/spree/api/v3/admin/invitations/acceptance_links_controller.rb +41 -0
- data/app/controllers/spree/api/v3/admin/me_controller.rb +11 -0
- data/app/controllers/spree/api/v3/admin/orders/claims_controller.rb +14 -1
- data/app/controllers/spree/api/v3/admin/orders/exchanges_controller.rb +22 -1
- data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +22 -1
- data/app/controllers/spree/api/v3/admin/orders_controller.rb +27 -1
- data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +31 -11
- data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +1 -19
- data/app/controllers/spree/api/v3/admin/prices_controller.rb +33 -2
- data/app/controllers/spree/api/v3/admin/products_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/resource_controller.rb +1 -0
- data/app/controllers/spree/api/v3/admin/sellers/invitations/acceptance_links_controller.rb +37 -0
- data/app/controllers/spree/api/v3/admin/setup_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/store_controller.rb +4 -0
- data/app/controllers/spree/api/v3/admin/translations/batches_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/translations_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/webhook_deliveries_controller.rb +12 -0
- data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +27 -0
- data/app/controllers/spree/api/v3/base_controller.rb +7 -3
- data/app/controllers/spree/api/v3/resource_controller.rb +11 -1
- data/app/controllers/spree/api/v3/seller/auth_controller.rb +1 -1
- data/app/controllers/spree/api/v3/seller/invitation_acceptances_controller.rb +5 -2
- data/app/controllers/spree/api/v3/seller/invitations/acceptance_links_controller.rb +34 -0
- data/app/controllers/spree/api/v3/seller/me_controller.rb +25 -2
- data/app/controllers/spree/api/v3/seller/orders_controller.rb +19 -24
- data/app/controllers/spree/api/v3/seller/password_resets_controller.rb +1 -1
- data/app/controllers/spree/api/v3/seller/resource_controller.rb +4 -0
- data/app/controllers/spree/api/v3/store/carts/items_controller.rb +7 -4
- data/app/controllers/spree/api/v3/store/carts/payment_sessions_controller.rb +10 -1
- data/app/controllers/spree/api/v3/store/carts/payments_controller.rb +5 -1
- data/app/controllers/spree/api/v3/store/companies/members_controller.rb +10 -11
- data/app/controllers/spree/api/v3/store/companies/orders_controller.rb +6 -0
- data/app/controllers/spree/api/v3/store/products/filters_controller.rb +1 -0
- data/app/controllers/spree/api/v3/store/products_controller.rb +6 -12
- data/app/controllers/spree/api/v3/store/resource_controller.rb +4 -0
- data/app/controllers/spree/api/v3/store/wishlist_items_controller.rb +11 -0
- data/app/controllers/spree/api/v3/webhooks/fulfillments_controller.rb +10 -6
- data/app/controllers/spree/api/v3/webhooks/payments_controller.rb +6 -2
- data/app/controllers/spree/api/v3/webhooks/payouts_controller.rb +6 -2
- data/app/jobs/spree/webhook_delivery_job.rb +18 -4
- data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +6 -0
- data/app/serializers/spree/api/v3/admin/invitation_acceptance_link_serializer.rb +15 -0
- data/app/serializers/spree/api/v3/admin/invitation_serializer.rb +0 -11
- data/app/serializers/spree/api/v3/admin/order_group_serializer.rb +7 -0
- data/app/serializers/spree/api/v3/admin/store_serializer.rb +8 -0
- data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +6 -2
- data/app/serializers/spree/api/v3/gift_card_serializer.rb +3 -1
- data/app/serializers/spree/api/v3/line_item_serializer.rb +2 -1
- data/app/serializers/spree/api/v3/market_serializer.rb +5 -0
- data/app/serializers/spree/api/v3/product_filters_serializer.rb +3 -0
- data/app/serializers/spree/api/v3/seller/account_serializer.rb +19 -0
- data/app/serializers/spree/api/v3/seller/invitation_acceptance_link_serializer.rb +15 -0
- data/app/serializers/spree/api/v3/seller/invitation_serializer.rb +1 -10
- data/app/services/spree/api/v3/filters_aggregator.rb +5 -2
- data/config/routes.rb +7 -0
- data/lib/spree/api/dependencies.rb +3 -0
- data/lib/spree/api/openapi/schema_helper.rb +10 -4
- metadata +14 -6
|
@@ -122,7 +122,7 @@ module Spree
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def permitted_params
|
|
125
|
-
|
|
125
|
+
normalize_params(
|
|
126
126
|
params.permit(
|
|
127
127
|
:name, :description, :position,
|
|
128
128
|
:starts_at, :ends_at, :match_policy,
|
|
@@ -132,24 +132,6 @@ module Spree
|
|
|
132
132
|
prices: [:id, :variant_id, :currency, :min_quantity, :amount, :compare_at_amount]
|
|
133
133
|
)
|
|
134
134
|
)
|
|
135
|
-
reject_foreign_variants(attrs)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# The price rows resolve variant ids with no store scoping, so a
|
|
139
|
-
# list in this store could otherwise be populated with another
|
|
140
|
-
# store's variants. Drop any id that isn't in the current store
|
|
141
|
-
# before assignment.
|
|
142
|
-
def reject_foreign_variants(attrs)
|
|
143
|
-
if attrs[:prices].present?
|
|
144
|
-
incoming = Array(attrs[:prices])
|
|
145
|
-
store_variant_ids = current_store.variants.where(id: incoming.map { |r| r[:variant_id] }.compact).
|
|
146
|
-
pluck(:id).map(&:to_s).to_set
|
|
147
|
-
attrs[:prices] = incoming.select do |row|
|
|
148
|
-
row[:variant_id].blank? || store_variant_ids.include?(row[:variant_id].to_s)
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
attrs
|
|
153
135
|
end
|
|
154
136
|
|
|
155
137
|
private
|
|
@@ -58,6 +58,10 @@ module Spree
|
|
|
58
58
|
# validations (docs/plans/6.0-volume-pricing.md). Each branch is
|
|
59
59
|
# guarded on the key its own failure carries, so a failure mode the
|
|
60
60
|
# service grows later is not reported under one of these names.
|
|
61
|
+
#
|
|
62
|
+
# `I18n.t` rather than `Spree.t` for these: the models raise them
|
|
63
|
+
# under `activerecord.errors`, and `Spree.t` would scope the lookup
|
|
64
|
+
# to `spree.` and miss the key.
|
|
61
65
|
if (invalid = result.error&.value.try(:[], :invalid_quantities))
|
|
62
66
|
return render_error(
|
|
63
67
|
code: 'invalid_min_quantity',
|
|
@@ -70,16 +74,34 @@ module Spree
|
|
|
70
74
|
if (negative = result.error&.value.try(:[], :invalid_amounts))
|
|
71
75
|
return render_error(
|
|
72
76
|
code: 'invalid_amount',
|
|
73
|
-
message:
|
|
77
|
+
message: I18n.t('activerecord.errors.models.spree/price_list.attributes.base.negative_price').upcase_first,
|
|
74
78
|
status: :unprocessable_content,
|
|
75
79
|
details: { rows: negative }
|
|
76
80
|
)
|
|
77
81
|
end
|
|
78
82
|
|
|
83
|
+
if (misplaced = result.error&.value.try(:[], :quantities_on_base_prices))
|
|
84
|
+
return render_error(
|
|
85
|
+
code: 'quantity_on_base_price',
|
|
86
|
+
message: I18n.t('activerecord.errors.models.spree/price_list.attributes.base.quantity_on_base_price').upcase_first,
|
|
87
|
+
status: :unprocessable_content,
|
|
88
|
+
details: { rows: misplaced }
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if (duplicates = result.error&.value.try(:[], :duplicate_quantities))
|
|
93
|
+
return render_error(
|
|
94
|
+
code: 'duplicate_min_quantity',
|
|
95
|
+
message: I18n.t('activerecord.errors.models.spree/price_list.attributes.base.duplicate_quantity').upcase_first,
|
|
96
|
+
status: :unprocessable_content,
|
|
97
|
+
details: { rows: duplicates }
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
79
101
|
if (over_cap = result.error&.value.try(:[], :over_cap))
|
|
80
102
|
return render_error(
|
|
81
103
|
code: 'too_many_breaks',
|
|
82
|
-
message:
|
|
104
|
+
message: I18n.t(
|
|
83
105
|
'activerecord.errors.models.spree/price.attributes.min_quantity.too_many_breaks',
|
|
84
106
|
count: Spree::Price::MAXIMUM_BREAKS_PER_VARIANT
|
|
85
107
|
).upcase_first,
|
|
@@ -88,6 +110,15 @@ module Spree
|
|
|
88
110
|
)
|
|
89
111
|
end
|
|
90
112
|
|
|
113
|
+
if (rising = result.error&.value.try(:[], :rising_ladders))
|
|
114
|
+
return render_error(
|
|
115
|
+
code: 'price_rises_with_quantity',
|
|
116
|
+
message: I18n.t('activerecord.errors.models.spree/price_list.attributes.base.price_rises_with_quantity').upcase_first,
|
|
117
|
+
status: :unprocessable_content,
|
|
118
|
+
details: { ladders: rising }
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
91
122
|
render json: result.value
|
|
92
123
|
end
|
|
93
124
|
|
|
@@ -301,7 +301,7 @@ module Spree
|
|
|
301
301
|
end
|
|
302
302
|
|
|
303
303
|
def search_provider
|
|
304
|
-
@search_provider ||= Spree::SearchProvider::Database.new(current_store)
|
|
304
|
+
@search_provider ||= Spree::SearchProvider::Database.new(current_store, audience: nil)
|
|
305
305
|
end
|
|
306
306
|
|
|
307
307
|
# Tag changes can flip automatic-collection matches, and `Tags::Bulk*`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Sellers
|
|
6
|
+
module Invitations
|
|
7
|
+
# The acceptance link of a pending invitation onto a seller's team.
|
|
8
|
+
#
|
|
9
|
+
# The link carries the token that joins the team, so reading
|
|
10
|
+
# sellers is not enough: the caller must be able to change the
|
|
11
|
+
# seller, as when sending the invitation.
|
|
12
|
+
class AcceptanceLinksController < Admin::BaseController
|
|
13
|
+
scoped_resource :sellers
|
|
14
|
+
|
|
15
|
+
# GET /api/v3/admin/sellers/:seller_id/invitations/:invitation_id/acceptance_link
|
|
16
|
+
def show
|
|
17
|
+
seller = current_store.sellers.find_by_prefix_id!(params[:seller_id])
|
|
18
|
+
authorize!(:update, seller)
|
|
19
|
+
invitation = seller.invitations.pending.find_by_prefix_id!(params[:invitation_id])
|
|
20
|
+
|
|
21
|
+
render json: Spree.api.admin_invitation_acceptance_link_serializer.new(
|
|
22
|
+
invitation, params: { store: current_store }
|
|
23
|
+
).to_h
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def read_actions
|
|
29
|
+
[]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -65,7 +65,7 @@ module Spree
|
|
|
65
65
|
|
|
66
66
|
# Outside the lock: the loader needs the admin committed, and it
|
|
67
67
|
# runs for minutes, so it is queued rather than awaited.
|
|
68
|
-
Spree::SampleData::LoadJob.perform_later if sample_data_requested?
|
|
68
|
+
Spree::SampleData::LoadJob.perform_later(store.id) if sample_data_requested?
|
|
69
69
|
|
|
70
70
|
refresh_token = Spree::RefreshToken.create_for(user, audience: JWT_AUDIENCE_ADMIN, request_env: request_env_for_token)
|
|
71
71
|
set_refresh_cookie(refresh_token)
|
|
@@ -105,6 +105,10 @@ module Spree
|
|
|
105
105
|
:preferred_payout_provider,
|
|
106
106
|
:preferred_default_payouts_schedule_interval,
|
|
107
107
|
:preferred_default_minimum_payout_amount,
|
|
108
|
+
:preferred_auto_approve_sellers,
|
|
109
|
+
:preferred_auto_approve_seller_products,
|
|
110
|
+
:preferred_send_seller_transactional_emails,
|
|
111
|
+
:preferred_default_commission_tax_rate,
|
|
108
112
|
:preferred_document_number_format,
|
|
109
113
|
:preferred_order_number_prefix,
|
|
110
114
|
:preferred_order_number_suffix,
|
|
@@ -39,7 +39,7 @@ module Spree
|
|
|
39
39
|
raw = params[:translations]
|
|
40
40
|
return render_empty_batch_error unless raw.is_a?(Array) && raw.any?
|
|
41
41
|
|
|
42
|
-
batch = Spree::Translations::Batch.new(batch_params)
|
|
42
|
+
batch = Spree::Translations::Batch.new(batch_params, store: current_store)
|
|
43
43
|
return unless require_batch_scopes!(batch)
|
|
44
44
|
|
|
45
45
|
records = batch.process! { |record| authorize!(:update, record) }
|
|
@@ -43,7 +43,7 @@ module Spree
|
|
|
43
43
|
# translation tables.
|
|
44
44
|
def parent_relation
|
|
45
45
|
klass = parent_lookup.klass
|
|
46
|
-
relation = klass.respond_to?(:
|
|
46
|
+
relation = klass.respond_to?(:translatable_scope) ? klass.translatable_scope(current_store) : klass.for_store(current_store)
|
|
47
47
|
|
|
48
48
|
children = klass.try(:translatable_children)
|
|
49
49
|
children ? relation.includes(children => :translations) : relation
|
|
@@ -42,6 +42,18 @@ module Spree
|
|
|
42
42
|
params[:webhook_endpoint_id]
|
|
43
43
|
)
|
|
44
44
|
end
|
|
45
|
+
|
|
46
|
+
# The payload is the record the event is about, so it is shown only
|
|
47
|
+
# to a caller who could read that record; the webhooks permission
|
|
48
|
+
# alone sees the delivery's status and response.
|
|
49
|
+
def serializer_params
|
|
50
|
+
super.merge(
|
|
51
|
+
payload_visible: lambda { |delivery|
|
|
52
|
+
key = delivery.payload_permission_key
|
|
53
|
+
key.nil? || holds_permission?(key)
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
end
|
|
45
57
|
end
|
|
46
58
|
end
|
|
47
59
|
end
|
|
@@ -8,6 +8,8 @@ module Spree
|
|
|
8
8
|
class WebhookEndpointsController < ResourceController
|
|
9
9
|
scoped_resource :webhooks
|
|
10
10
|
|
|
11
|
+
before_action :reject_unauthorized_credential_subscription!, only: [:create, :update]
|
|
12
|
+
|
|
11
13
|
# POST /api/v3/admin/webhook_endpoints/:id/send_test
|
|
12
14
|
#
|
|
13
15
|
# Fires a synthetic `webhook.test` delivery so admins can verify the
|
|
@@ -68,6 +70,31 @@ module Spree
|
|
|
68
70
|
def permitted_params
|
|
69
71
|
params.permit(*model_additional_permitted_attributes, :name, :url, :active, subscriptions: [])
|
|
70
72
|
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Customer password reset tokens are account credentials, so the
|
|
77
|
+
# webhooks permission alone cannot subscribe an endpoint to them, or
|
|
78
|
+
# repoint an endpoint that already receives them.
|
|
79
|
+
def reject_unauthorized_credential_subscription!
|
|
80
|
+
return if holds_permission?('write_customers')
|
|
81
|
+
|
|
82
|
+
requested = Array(permitted_params[:subscriptions]) & Spree::WebhookEndpoint::CREDENTIAL_EVENTS
|
|
83
|
+
return if requested.empty? && !repoints_credential_endpoint?
|
|
84
|
+
|
|
85
|
+
render_error(
|
|
86
|
+
code: Spree::Api::V3::ErrorHandler::ERROR_CODES[:access_denied],
|
|
87
|
+
message: "Receiving #{Spree::WebhookEndpoint::CREDENTIAL_EVENTS.to_sentence} requires permission to manage customers",
|
|
88
|
+
status: :forbidden
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def repoints_credential_endpoint?
|
|
93
|
+
return false unless action_name == 'update'
|
|
94
|
+
return false unless permitted_params.key?(:url) || permitted_params.key?(:subscriptions)
|
|
95
|
+
|
|
96
|
+
current_store.webhook_endpoints.find_by_prefix_id(params[:id])&.receives_credentials?
|
|
97
|
+
end
|
|
71
98
|
end
|
|
72
99
|
end
|
|
73
100
|
end
|
|
@@ -81,7 +81,10 @@ module Spree
|
|
|
81
81
|
if errors.is_a?(ActiveModel::Errors)
|
|
82
82
|
render_validation_error(errors)
|
|
83
83
|
else
|
|
84
|
-
|
|
84
|
+
# The unwrapped value, so a workflow's rejection symbol reaches
|
|
85
|
+
# the branch that translates it rather than the one that prints
|
|
86
|
+
# the wrapper.
|
|
87
|
+
render_service_error(errors)
|
|
85
88
|
end
|
|
86
89
|
end
|
|
87
90
|
|
|
@@ -108,10 +111,11 @@ module Spree
|
|
|
108
111
|
try_spree_current_user
|
|
109
112
|
end
|
|
110
113
|
|
|
111
|
-
# CanCanCan ability
|
|
114
|
+
# CanCanCan ability, built from the swappable
|
|
115
|
+
# `Spree::Dependencies.ability_class` (Spree::Ability by default).
|
|
112
116
|
# @return [Spree::Ability]
|
|
113
117
|
def current_ability
|
|
114
|
-
@current_ability ||= Spree
|
|
118
|
+
@current_ability ||= Spree.ability_class.new(current_user, ability_options)
|
|
115
119
|
end
|
|
116
120
|
|
|
117
121
|
# Options passed to the CanCanCan ability
|
|
@@ -254,7 +254,7 @@ module Spree
|
|
|
254
254
|
|
|
255
255
|
@search = scope.includes(collection_includes).
|
|
256
256
|
preload_associations_lazily.
|
|
257
|
-
ransack(ransack_params)
|
|
257
|
+
ransack(ransack_params, auth_object: ransack_auth_object)
|
|
258
258
|
result = @search.result(distinct: collection_distinct?)
|
|
259
259
|
pagy_options = { limit: limit, page: page }
|
|
260
260
|
result = apply_collection_sort(result)
|
|
@@ -262,6 +262,16 @@ module Spree
|
|
|
262
262
|
@collection
|
|
263
263
|
end
|
|
264
264
|
|
|
265
|
+
# Who is filtering, as Ransack's auth object. Nil is the back office,
|
|
266
|
+
# which may use every allowlisted attribute; the Store and Seller
|
|
267
|
+
# branches name themselves so the models narrow what they may reach
|
|
268
|
+
# (see Spree::RansackableAttributes). Covers sorting too.
|
|
269
|
+
#
|
|
270
|
+
# @return [Symbol, nil]
|
|
271
|
+
def ransack_auth_object
|
|
272
|
+
nil
|
|
273
|
+
end
|
|
274
|
+
|
|
265
275
|
# Override in subclass to disable distinct (e.g., for custom sorting with computed columns)
|
|
266
276
|
# @return [Boolean] whether to apply distinct to the collection
|
|
267
277
|
def collection_distinct?
|
|
@@ -136,7 +136,7 @@ module Spree
|
|
|
136
136
|
def auth_response(user)
|
|
137
137
|
{
|
|
138
138
|
token: generate_jwt(user, audience: Spree::Api::V3::JwtAuthentication::JWT_AUDIENCE_SELLER),
|
|
139
|
-
user: Spree.api.
|
|
139
|
+
user: Spree.api.seller_account_serializer.new(user, params: { store: user.sellers.first&.store }).to_h,
|
|
140
140
|
sellers: serialized_sellers(user)
|
|
141
141
|
}
|
|
142
142
|
end
|
|
@@ -94,7 +94,10 @@ module Spree
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def authenticate_existing(user)
|
|
97
|
-
|
|
97
|
+
# Under the login lockout: otherwise this is an unthrottled way to
|
|
98
|
+
# guess an existing account's password with an invitation token.
|
|
99
|
+
verdict = Spree::Authentication::Lockout.check(user) { user.valid_password?(params[:password].to_s) }
|
|
100
|
+
return user if verdict == :valid
|
|
98
101
|
|
|
99
102
|
render_error(
|
|
100
103
|
code: ErrorHandler::ERROR_CODES[:authentication_failed],
|
|
@@ -127,7 +130,7 @@ module Spree
|
|
|
127
130
|
def auth_response(user)
|
|
128
131
|
{
|
|
129
132
|
token: generate_jwt(user, audience: Spree::Api::V3::JwtAuthentication::JWT_AUDIENCE_SELLER),
|
|
130
|
-
user: Spree.api.
|
|
133
|
+
user: Spree.api.seller_account_serializer.new(
|
|
131
134
|
user, params: { store: @invitation.store }
|
|
132
135
|
).to_h,
|
|
133
136
|
sellers: serialized_sellers(user)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Seller
|
|
5
|
+
module Invitations
|
|
6
|
+
# The acceptance link of a pending invitation onto this seller's team.
|
|
7
|
+
#
|
|
8
|
+
# The link carries the token that joins the team, so it is authorized
|
|
9
|
+
# like inviting someone: write access to the seller profile. Rooted at
|
|
10
|
+
# `current_seller.invitations`, so another seller's invitation reads
|
|
11
|
+
# as missing.
|
|
12
|
+
class AcceptanceLinksController < Seller::BaseController
|
|
13
|
+
scoped_resource :seller_profile
|
|
14
|
+
|
|
15
|
+
# GET /api/v3/seller/invitations/:invitation_id/acceptance_link
|
|
16
|
+
def show
|
|
17
|
+
invitation = current_seller.invitations.pending.find_by_prefix_id!(params[:invitation_id])
|
|
18
|
+
|
|
19
|
+
render json: Spree.api.seller_invitation_acceptance_link_serializer.new(
|
|
20
|
+
invitation, params: { store: current_store }
|
|
21
|
+
).to_h
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
def read_actions
|
|
27
|
+
[]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -20,11 +20,32 @@ module Spree
|
|
|
20
20
|
render json: me_response
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# Self-service update of the signed-in person's own account — the
|
|
24
|
+
# name and photo their team sees, and the panel's language. It
|
|
25
|
+
# operates on `current_user` directly, so it needs no per-record
|
|
26
|
+
# authorization, and no seller role either: this is theirs whichever
|
|
27
|
+
# seller they are acting for.
|
|
28
|
+
#
|
|
29
|
+
# Distinct from PATCH /profile, which writes the seller business.
|
|
30
|
+
# `avatar` accepts an ActiveStorage direct-upload signed id to set
|
|
31
|
+
# the photo, or `null` to remove it.
|
|
32
|
+
def update
|
|
33
|
+
if current_user.update(permitted_params)
|
|
34
|
+
render json: me_response
|
|
35
|
+
else
|
|
36
|
+
render_validation_error(current_user.errors)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
23
40
|
private
|
|
24
41
|
|
|
42
|
+
def permitted_params
|
|
43
|
+
params.permit(:selected_locale, :first_name, :last_name, :avatar)
|
|
44
|
+
end
|
|
45
|
+
|
|
25
46
|
def me_response
|
|
26
47
|
{
|
|
27
|
-
user: Spree.api.
|
|
48
|
+
user: Spree.api.seller_account_serializer.new(current_user, params: serializer_params).to_h,
|
|
28
49
|
sellers: serialized_sellers,
|
|
29
50
|
# Both shapes, exactly as the admin `/me`: the panel's `<Can>`
|
|
30
51
|
# reads CanCanCan rules, the key gate reads keys. Sending only
|
|
@@ -44,7 +65,9 @@ module Spree
|
|
|
44
65
|
# Empty until a seller is named: capability is per seller, so there is
|
|
45
66
|
# no meaningful answer spanning all of them. A bare ability (no
|
|
46
67
|
# resource) serializes to nothing rather than raising, so the panel
|
|
47
|
-
# can call `/me` before choosing a seller.
|
|
68
|
+
# can call `/me` before choosing a seller. Deliberately the core
|
|
69
|
+
# class, not `Spree.ability_class`: a custom ability may add rules
|
|
70
|
+
# after `super`, and those must not leak into the no-seller answer.
|
|
48
71
|
def seller_ability
|
|
49
72
|
return Spree::Ability.new(nil) if current_seller.nil?
|
|
50
73
|
|
|
@@ -77,7 +77,11 @@ module Spree
|
|
|
77
77
|
# each lay their fields over the same snapshot, and the later one
|
|
78
78
|
# would undo the earlier one's lines.
|
|
79
79
|
with_order_lock do
|
|
80
|
-
|
|
80
|
+
addresses = corrected_addresses
|
|
81
|
+
invalid = addresses.values.find(&:invalid?)
|
|
82
|
+
next render_validation_error(invalid.errors) if invalid
|
|
83
|
+
|
|
84
|
+
if @resource.update(addresses)
|
|
81
85
|
render json: serialize_resource(@resource.reload)
|
|
82
86
|
else
|
|
83
87
|
render_validation_error(@resource.errors)
|
|
@@ -115,9 +119,10 @@ module Spree
|
|
|
115
119
|
# Either address, as a correction rather than a replacement: the
|
|
116
120
|
# sent fields are laid over what the order already holds, so a
|
|
117
121
|
# request naming one line does not blank out the country and
|
|
118
|
-
# postcode beside it.
|
|
119
|
-
#
|
|
120
|
-
#
|
|
122
|
+
# postcode beside it. Written as a fresh ownerless snapshot straight
|
|
123
|
+
# onto the order — never through the buyer's nested writer, which
|
|
124
|
+
# files the address in their book and makes it their default for
|
|
125
|
+
# every later checkout on the marketplace.
|
|
121
126
|
def sent_addresses
|
|
122
127
|
@sent_addresses ||= params.permit(
|
|
123
128
|
shipping_address: address_permitted_keys,
|
|
@@ -125,32 +130,22 @@ module Spree
|
|
|
125
130
|
).slice(:shipping_address, :billing_address).reject { |_key, value| value.blank? }
|
|
126
131
|
end
|
|
127
132
|
|
|
128
|
-
def
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if permitted[:shipping_address].present?
|
|
133
|
-
attributes[:ship_address_attributes] =
|
|
134
|
-
merged_address(@resource.ship_address, permitted[:shipping_address])
|
|
133
|
+
def corrected_addresses
|
|
134
|
+
{}.tap do |addresses|
|
|
135
|
+
if sent_addresses[:shipping_address].present?
|
|
136
|
+
addresses[:ship_address] = corrected_address(@resource.ship_address, sent_addresses[:shipping_address])
|
|
135
137
|
end
|
|
136
138
|
|
|
137
|
-
if
|
|
138
|
-
|
|
139
|
-
merged_address(@resource.bill_address, permitted[:billing_address])
|
|
139
|
+
if sent_addresses[:billing_address].present?
|
|
140
|
+
addresses[:bill_address] = corrected_address(@resource.bill_address, sent_addresses[:billing_address])
|
|
140
141
|
end
|
|
141
142
|
end
|
|
142
143
|
end
|
|
143
144
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# The permitted keys that are real columns; the rest are the writer
|
|
150
|
-
# aliases a client may send, which have no value to carry over.
|
|
151
|
-
def address_attribute_names
|
|
152
|
-
@address_attribute_names ||=
|
|
153
|
-
address_permitted_keys.map(&:to_s) & Spree::Address.column_names
|
|
145
|
+
def corrected_address(address, sent)
|
|
146
|
+
# No label: an address-book label means nothing on an order's copy,
|
|
147
|
+
# and labels are unique per owner, which an ownerless copy lacks.
|
|
148
|
+
(address&.snapshot || Spree::Address.new).tap { |copy| copy.assign_attributes(sent.to_h.except('label', :label)) }
|
|
154
149
|
end
|
|
155
150
|
|
|
156
151
|
def address_permitted_keys
|
|
@@ -128,7 +128,7 @@ module Spree
|
|
|
128
128
|
def auth_response(user)
|
|
129
129
|
{
|
|
130
130
|
token: generate_jwt(user, audience: Spree::Api::V3::JwtAuthentication::JWT_AUDIENCE_SELLER),
|
|
131
|
-
user: Spree.api.
|
|
131
|
+
user: Spree.api.seller_account_serializer.new(
|
|
132
132
|
user, params: { store: seller_store(user) }
|
|
133
133
|
).to_h,
|
|
134
134
|
sellers: serialized_sellers(user)
|
|
@@ -97,12 +97,15 @@ module Spree
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
# Extension attributes ride in `options`, which is how AddItem
|
|
100
|
-
# forwards per-line-item values onto the record.
|
|
100
|
+
# forwards per-line-item values onto the record. Only those reach
|
|
101
|
+
# it: anything else in `options` is internal to AddItem (the
|
|
102
|
+
# fulfillment an admin pins a line to) and not the shopper's to set.
|
|
101
103
|
def item_options
|
|
102
|
-
keys = Spree::LineItem.additional_permitted_attributes.flat_map { |a| a.is_a?(Hash) ? a.keys : a }
|
|
103
|
-
extras = permitted_params.to_h.slice(*keys.map(&:to_s)).symbolize_keys
|
|
104
|
+
keys = Spree::LineItem.additional_permitted_attributes.flat_map { |a| a.is_a?(Hash) ? a.keys : a }.map(&:to_s)
|
|
104
105
|
|
|
105
|
-
(permitted_params[:options] || {}).to_h.
|
|
106
|
+
(permitted_params[:options] || {}).to_h.slice(*keys).
|
|
107
|
+
merge(permitted_params.to_h.slice(*keys)).
|
|
108
|
+
symbolize_keys
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
def permitted_params
|
|
@@ -13,7 +13,16 @@ module Spree
|
|
|
13
13
|
# POST /api/v3/store/carts/:cart_id/payment_sessions
|
|
14
14
|
def create
|
|
15
15
|
with_order_lock do
|
|
16
|
-
payment_method = current_store.payment_methods.
|
|
16
|
+
payment_method = current_store.payment_methods.active.storefront_visible.
|
|
17
|
+
find_by_prefix_id!(permitted_params[:payment_method_id])
|
|
18
|
+
|
|
19
|
+
unless payment_method.available_for_order?(@cart)
|
|
20
|
+
return render_error(
|
|
21
|
+
code: 'payment_method_unavailable',
|
|
22
|
+
message: Spree.t('api.v3.payments.method_unavailable'),
|
|
23
|
+
status: :unprocessable_content
|
|
24
|
+
)
|
|
25
|
+
end
|
|
17
26
|
|
|
18
27
|
@payment_session = payment_method.create_payment_session(
|
|
19
28
|
order: @cart,
|
|
@@ -11,7 +11,11 @@ module Spree
|
|
|
11
11
|
# POST /api/v3/store/carts/:cart_id/payments
|
|
12
12
|
# Creates a payment for non-session payment methods (e.g. Check, Cash on Delivery, Bank Transfer)
|
|
13
13
|
def create
|
|
14
|
-
|
|
14
|
+
# The same methods the cart offers: a disabled or staff-only
|
|
15
|
+
# method (an offline one marks the order paid) must not be
|
|
16
|
+
# reachable by naming its id.
|
|
17
|
+
payment_method = current_store.payment_methods.active.storefront_visible.
|
|
18
|
+
find_by_prefix_id!(params[:payment_method_id])
|
|
15
19
|
|
|
16
20
|
if payment_method.session_required?
|
|
17
21
|
return render_error(
|
|
@@ -4,26 +4,25 @@ module Spree
|
|
|
4
4
|
module Store
|
|
5
5
|
module Companies
|
|
6
6
|
# The people with standing over one node, listed and added by its
|
|
7
|
-
# members. Adding takes an email and
|
|
8
|
-
#
|
|
9
|
-
# the
|
|
10
|
-
#
|
|
7
|
+
# members. Adding takes an email and always sends an invitation,
|
|
8
|
+
# even to an existing customer: a member typing an address cannot
|
|
9
|
+
# vouch that it is the person they mean, so the invitee accepts with
|
|
10
|
+
# the emailed token. The response is the same whether or not the
|
|
11
|
+
# email has an account. Any member can invite; restraint is
|
|
12
|
+
# Enterprise's policy layer.
|
|
11
13
|
class MembersController < BaseController
|
|
12
14
|
# POST /api/v3/store/companies/:company_id/members
|
|
13
15
|
def create
|
|
14
16
|
result = Spree.company_add_member_service.call(
|
|
15
17
|
company: @parent,
|
|
16
18
|
email: params.require(:customer_email),
|
|
17
|
-
inviter: current_user
|
|
19
|
+
inviter: current_user,
|
|
20
|
+
require_acceptance: true
|
|
18
21
|
)
|
|
19
22
|
|
|
20
23
|
if result.success?
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
else
|
|
24
|
-
serializer_class
|
|
25
|
-
end
|
|
26
|
-
render json: serializer.new(result.value, params: serializer_params).to_h, status: :created
|
|
24
|
+
render json: Spree.api.company_invitation_serializer.new(result.value, params: serializer_params).to_h,
|
|
25
|
+
status: :created
|
|
27
26
|
else
|
|
28
27
|
render_validation_error(result.value.errors)
|
|
29
28
|
end
|
|
@@ -20,6 +20,12 @@ module Spree
|
|
|
20
20
|
current_store.orders.complete.
|
|
21
21
|
where(company_id: @parent.self_and_descendants.select(:id))
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
# These are other members' orders: gift card codes and download
|
|
25
|
+
# links are the buyer's credentials, not the company's.
|
|
26
|
+
def serializer_params
|
|
27
|
+
super.merge(hide_credentials: true)
|
|
28
|
+
end
|
|
23
29
|
end
|
|
24
30
|
end
|
|
25
31
|
end
|