spree_api 6.0.0.beta1 → 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/admin/stock_receipt_actions.rb +1 -1
- data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +11 -0
- data/app/controllers/concerns/spree/api/v3/current_password_confirmation.rb +8 -0
- data/app/controllers/concerns/spree/api/v3/orders/claim_actions.rb +2 -2
- data/app/controllers/concerns/spree/api/v3/orders/exchange_actions.rb +3 -3
- data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +15 -2
- data/app/controllers/concerns/spree/api/v3/orders/post_sale_actions.rb +1 -1
- data/app/controllers/concerns/spree/api/v3/orders/return_actions.rb +3 -3
- 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/api_keys_controller.rb +2 -2
- 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/refunds_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 +41 -9
- 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 +9 -1
- data/app/controllers/spree/api/v3/admin/store_controller.rb +5 -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 +22 -3
- data/app/controllers/spree/api/v3/resource_controller.rb +27 -2
- 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 +20 -25
- 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/actor_serializer.rb +28 -0
- data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +14 -1
- data/app/serializers/spree/api/v3/admin/claim_serializer.rb +3 -4
- data/app/serializers/spree/api/v3/admin/exchange_serializer.rb +3 -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/line_item_serializer.rb +28 -1
- data/app/serializers/spree/api/v3/admin/order_group_serializer.rb +7 -0
- data/app/serializers/spree/api/v3/admin/order_serializer.rb +7 -24
- data/app/serializers/spree/api/v3/admin/refund_serializer.rb +6 -0
- data/app/serializers/spree/api/v3/admin/return_serializer.rb +2 -3
- data/app/serializers/spree/api/v3/admin/saved_report_serializer.rb +3 -4
- data/app/serializers/spree/api/v3/admin/stock_receipt_serializer.rb +2 -3
- data/app/serializers/spree/api/v3/admin/store_serializer.rb +10 -0
- data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +6 -2
- data/app/serializers/spree/api/v3/base_serializer.rb +22 -0
- 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/configuration.rb +19 -14
- data/lib/spree/api/dependencies.rb +4 -0
- data/lib/spree/api/engine.rb +6 -0
- data/lib/spree/api/openapi/schema_helper.rb +10 -4
- metadata +15 -6
|
@@ -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
|
|
|
@@ -43,7 +43,7 @@ module Spree
|
|
|
43
43
|
with_order_lock do
|
|
44
44
|
result = Spree.order_cancel_workflow.call(
|
|
45
45
|
order: @resource,
|
|
46
|
-
canceler:
|
|
46
|
+
canceler: current_actor,
|
|
47
47
|
reason: cancel_reason,
|
|
48
48
|
note: params[:cancel_note].presence,
|
|
49
49
|
refund_payments: params[:refund_payments].to_b,
|
|
@@ -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
|
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
8
|
-
#
|
|
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
|
|
26
|
-
|
|
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
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Whoever performed an action — an admin user, an API key, or a class
|
|
6
|
+
# an extension registered in `Spree.actor_classes`.
|
|
7
|
+
#
|
|
8
|
+
# One shape for every kind, so a client renders "cancelled by" the same
|
|
9
|
+
# way whether a person clicked the button or a warehouse connector
|
|
10
|
+
# called the endpoint. See docs/plans/6.0-action-actors.md.
|
|
11
|
+
class ActorSerializer < V3::BaseSerializer
|
|
12
|
+
typelize type: [:string, enum: Spree::Actor::BUILT_IN_KINDS, enum_type_name: 'ActorKind'],
|
|
13
|
+
label: [:string, nullable: true]
|
|
14
|
+
|
|
15
|
+
# `admin_user` / `api_key`, never the Ruby class name.
|
|
16
|
+
attribute :type do |actor|
|
|
17
|
+
actor.actor_kind
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# What a timeline shows: a person's name or email, a key's name.
|
|
21
|
+
attribute :label do |actor|
|
|
22
|
+
actor.actor_label
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -19,6 +19,8 @@ module Spree
|
|
|
19
19
|
revoked_at: [:string, nullable: true],
|
|
20
20
|
last_used_at: [:string, nullable: true],
|
|
21
21
|
created_by_email: [:string, nullable: true],
|
|
22
|
+
created_by_type: [:string, nullable: true, enum: Spree::Actor::BUILT_IN_KINDS, enum_type_name: 'ActorKind'],
|
|
23
|
+
created_by_label: [:string, nullable: true],
|
|
22
24
|
channel_id: [:string, nullable: true]
|
|
23
25
|
|
|
24
26
|
attributes :name, :key_type, :token_prefix, :scopes,
|
|
@@ -37,8 +39,19 @@ module Spree
|
|
|
37
39
|
key.plaintext_token
|
|
38
40
|
end
|
|
39
41
|
|
|
42
|
+
# A key can be minted by another key, which has no email — so the
|
|
43
|
+
# address is answered only for the actors that have one, and
|
|
44
|
+
# `created_by_label` is what a list column should render.
|
|
40
45
|
attribute :created_by_email do |key|
|
|
41
|
-
key.created_by
|
|
46
|
+
key.created_by.try(:email)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
attribute :created_by_type do |key|
|
|
50
|
+
Spree::Base.polymorphic_api_type(key.created_by_type)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
attribute :created_by_label do |key|
|
|
54
|
+
key.created_by.try(:actor_label)
|
|
42
55
|
end
|
|
43
56
|
end
|
|
44
57
|
end
|
|
@@ -7,13 +7,12 @@ module Spree
|
|
|
7
7
|
class ClaimSerializer < V3::ClaimSerializer
|
|
8
8
|
typelize memo: [:string, nullable: true],
|
|
9
9
|
metadata: 'Record<string, unknown>',
|
|
10
|
-
created_by_id: [:string, nullable: true]
|
|
10
|
+
created_by_id: [:string, nullable: true],
|
|
11
|
+
created_by_type: [:string, nullable: true, enum: Spree::Actor::BUILT_IN_KINDS, enum_type_name: 'ActorKind']
|
|
11
12
|
|
|
12
13
|
attributes :memo, :metadata, created_at: :iso8601, updated_at: :iso8601
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
claim.created_by&.prefixed_id
|
|
16
|
-
end
|
|
15
|
+
actor_attributes :created_by
|
|
17
16
|
|
|
18
17
|
many :claim_line_items,
|
|
19
18
|
resource: proc { Spree.api.admin_claim_line_item_serializer },
|
|
@@ -8,7 +8,8 @@ module Spree
|
|
|
8
8
|
typelize memo: [:string, nullable: true],
|
|
9
9
|
metadata: 'Record<string, unknown>',
|
|
10
10
|
stock_location_id: [:string, nullable: true],
|
|
11
|
-
created_by_id: [:string, nullable: true]
|
|
11
|
+
created_by_id: [:string, nullable: true],
|
|
12
|
+
created_by_type: [:string, nullable: true, enum: Spree::Actor::BUILT_IN_KINDS, enum_type_name: 'ActorKind']
|
|
12
13
|
|
|
13
14
|
attributes :memo, :metadata, created_at: :iso8601, updated_at: :iso8601
|
|
14
15
|
|
|
@@ -16,9 +17,7 @@ module Spree
|
|
|
16
17
|
exchange.stock_location&.prefixed_id
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
exchange.created_by&.prefixed_id
|
|
21
|
-
end
|
|
20
|
+
actor_attributes :created_by
|
|
22
21
|
|
|
23
22
|
many :exchange_line_items,
|
|
24
23
|
resource: proc { Spree.api.admin_exchange_line_item_serializer },
|
|
@@ -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.
|