spree_api 6.0.0.rc4 → 6.0.0.rc6
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/auth_cookies.rb +23 -0
- data/app/controllers/concerns/spree/api/v3/admin/refund_authorization.rb +26 -0
- data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +1 -0
- data/app/controllers/concerns/spree/api/v3/allowed_redirect_url.rb +27 -0
- data/app/controllers/concerns/spree/api/v3/api_key_authentication.rb +14 -2
- data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +23 -4
- data/app/controllers/concerns/spree/api/v3/digital_assets_resource.rb +68 -0
- data/app/controllers/concerns/spree/api/v3/error_handler.rb +3 -0
- data/app/controllers/concerns/spree/api/v3/jwt_authentication.rb +27 -1
- data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +6 -1
- data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +6 -2
- data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +4 -1
- data/app/controllers/spree/api/v3/admin/auth_controller.rb +32 -5
- data/app/controllers/spree/api/v3/admin/commission_lines_controller.rb +2 -2
- data/app/controllers/spree/api/v3/admin/companies/addresses_controller.rb +7 -1
- data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +27 -8
- data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +4 -0
- data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +4 -0
- data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/customers_controller.rb +14 -14
- data/app/controllers/spree/api/v3/admin/delivery_profiles/origin_groups_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/delivery_profiles_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +1 -8
- data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +2 -0
- data/app/controllers/spree/api/v3/admin/me_controller.rb +2 -0
- data/app/controllers/spree/api/v3/admin/option_types_controller.rb +13 -0
- data/app/controllers/spree/api/v3/admin/orders/claims_controller.rb +2 -0
- data/app/controllers/spree/api/v3/admin/orders/exchanges_controller.rb +9 -0
- data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +7 -4
- data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +2 -0
- data/app/controllers/spree/api/v3/admin/orders_controller.rb +21 -7
- data/app/controllers/spree/api/v3/admin/package_types_controller.rb +2 -3
- data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +7 -1
- data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +18 -0
- data/app/controllers/spree/api/v3/admin/product_types_controller.rb +12 -1
- data/app/controllers/spree/api/v3/admin/products/digital_assets_controller.rb +5 -109
- data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +7 -5
- data/app/controllers/spree/api/v3/admin/products_controller.rb +33 -6
- data/app/controllers/spree/api/v3/admin/sellers/payouts_controller.rb +4 -3
- data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +3 -2
- data/app/controllers/spree/api/v3/admin/store_controller.rb +8 -1
- data/app/controllers/spree/api/v3/admin/tags_controller.rb +11 -9
- data/app/controllers/spree/api/v3/admin/variants_controller.rb +96 -1
- data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +5 -2
- data/app/controllers/spree/api/v3/oauth/authorizations_controller.rb +17 -6
- data/app/controllers/spree/api/v3/resource_controller.rb +50 -7
- data/app/controllers/spree/api/v3/seller/auth_controller.rb +10 -0
- data/app/controllers/spree/api/v3/seller/invitation_acceptances_controller.rb +2 -0
- data/app/controllers/spree/api/v3/seller/me_controller.rb +4 -0
- data/app/controllers/spree/api/v3/seller/orders_controller.rb +11 -3
- data/app/controllers/spree/api/v3/seller/package_types_controller.rb +2 -2
- data/app/controllers/spree/api/v3/seller/password_resets_controller.rb +5 -1
- data/app/controllers/spree/api/v3/seller/products/digital_assets_controller.rb +67 -0
- data/app/controllers/spree/api/v3/seller/products_controller.rb +11 -63
- data/app/controllers/spree/api/v3/seller/resource_controller.rb +2 -6
- data/app/controllers/spree/api/v3/seller/team_controller.rb +1 -1
- data/app/controllers/spree/api/v3/store/carts/addresses_controller.rb +53 -0
- data/app/controllers/spree/api/v3/store/carts/store_credits_controller.rb +9 -1
- data/app/controllers/spree/api/v3/store/carts_controller.rb +17 -3
- data/app/controllers/spree/api/v3/store/companies/addresses_controller.rb +4 -0
- data/app/controllers/spree/api/v3/store/customer/addresses_controller.rb +4 -0
- data/app/controllers/spree/api/v3/store/customer/data_requests_controller.rb +19 -1
- data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +3 -14
- data/app/controllers/spree/api/v3/store/customers_controller.rb +13 -1
- data/app/controllers/spree/api/v3/store/data_request_confirmations_controller.rb +23 -0
- data/app/controllers/spree/api/v3/store/data_requests_controller.rb +56 -0
- data/app/controllers/spree/api/v3/store/delivery_methods_controller.rb +6 -1
- data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +2 -11
- data/app/controllers/spree/api/v3/store/products/filters_controller.rb +19 -4
- data/app/request_schemas/spree/api/v3/admin/variant_request_schema.rb +21 -0
- data/app/request_schemas/spree/api/v3/digital_asset_request_schema.rb +25 -0
- data/app/request_schemas/spree/api/v3/seller/price_request_schema.rb +20 -0
- data/app/request_schemas/spree/api/v3/seller/variant_request_schema.rb +19 -0
- data/app/request_schemas/spree/api/v3/variant_request_schema.rb +8 -13
- data/app/serializers/concerns/spree/api/v3/digital_asset_details.rb +70 -0
- data/app/serializers/spree/api/v3/address_owner_serializer.rb +27 -0
- data/app/serializers/spree/api/v3/address_serializer.rb +5 -1
- data/app/serializers/spree/api/v3/admin/address_owner_serializer.rb +10 -0
- data/app/serializers/spree/api/v3/admin/address_serializer.rb +1 -10
- data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/catalog_assignment_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/catalog_price_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/collection_rule_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/collection_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/commission_line_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/commission_rate_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/consent_record_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/delivery_origin_group_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/delivery_profile_serializer.rb +3 -5
- data/app/serializers/spree/api/v3/admin/digital_asset_serializer.rb +3 -47
- data/app/serializers/spree/api/v3/admin/email_template_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/newsletter_subscriber_serializer.rb +1 -0
- data/app/serializers/spree/api/v3/admin/order_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/package_type_serializer.rb +3 -3
- data/app/serializers/spree/api/v3/admin/permission_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/price_history_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +9 -4
- data/app/serializers/spree/api/v3/admin/price_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/purchase_order_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/seller_payout_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/seller_requirement_status_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/seller_serializer.rb +3 -5
- data/app/serializers/spree/api/v3/admin/seller_transfer_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/shipping_label_serializer.rb +3 -3
- data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/stock_receipt_item_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/stock_receipt_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/stock_transfer_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +3 -5
- data/app/serializers/spree/api/v3/admin/store_serializer.rb +1 -0
- data/app/serializers/spree/api/v3/admin/tax_identifier_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/tax_line_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/variant_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/base_serializer.rb +33 -6
- data/app/serializers/spree/api/v3/claim_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/collection_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/company_invitation_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/company_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/data_request_confirmation_requested_event_serializer.rb +28 -0
- data/app/serializers/spree/api/v3/data_request_event_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/data_request_serializer.rb +8 -4
- data/app/serializers/spree/api/v3/delivery_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/delivery_zone_member_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/discount_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/file_upload_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/fulfillment_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/media_event_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/media_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/newsletter_subscriber_serializer.rb +5 -1
- data/app/serializers/spree/api/v3/option_type_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/order_group_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/order_serializer.rb +4 -2
- data/app/serializers/spree/api/v3/payment_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/product_filter_availability_option_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/product_filter_option_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/seller/address_serializer.rb +3 -2
- data/app/serializers/spree/api/v3/seller/digital_asset_serializer.rb +16 -0
- data/app/serializers/spree/api/v3/seller/order_serializer.rb +3 -3
- data/app/serializers/spree/api/v3/seller/package_type_serializer.rb +3 -3
- data/app/serializers/spree/api/v3/seller/payout_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/seller/profile_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/seller/requirement_status_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/seller/store_serializer.rb +18 -0
- data/app/serializers/spree/api/v3/seller/tax_identifier_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/seller/variant_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/shipping_label_event_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/stock_movement_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/stock_receipt_event_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/variant_serializer.rb +2 -2
- data/app/services/spree/api/v3/filter_table.rb +22 -3
- data/app/services/spree/api/v3/filters_aggregator.rb +63 -18
- data/app/services/spree/api/v3/request_schema.rb +64 -17
- data/config/locales/en.yml +2 -0
- data/config/routes.rb +22 -11
- data/lib/spree/api/agent_write_schemas.rb +15 -3
- data/lib/spree/api/dependencies.rb +4 -4
- data/lib/spree/api/engine.rb +6 -0
- data/lib/spree/api/enum_types.rb +118 -0
- data/lib/spree/api/middleware/no_index.rb +33 -0
- data/lib/spree/api/openapi/filter_documentation.rb +8 -1
- data/lib/spree/api/openapi/schema_helper.rb +2 -1
- data/lib/spree/api/testing_support/v3/base.rb +18 -0
- data/lib/spree/api/typelizer_generation.rb +5 -0
- data/lib/typelizer/open_enums.rb +40 -16
- metadata +25 -6
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Store
|
|
5
|
+
module Carts
|
|
6
|
+
# Every address this cart may be shipped or billed to, in one call:
|
|
7
|
+
# the buyer's own book, plus the book of the company the cart names
|
|
8
|
+
# — that node's entries and the ones it inherits from the nodes above
|
|
9
|
+
# it — limited to the countries the cart's market sells to.
|
|
10
|
+
#
|
|
11
|
+
# A checkout address step would otherwise merge two books and filter
|
|
12
|
+
# them by the market itself. Asking the cart keeps the picker and the
|
|
13
|
+
# checkout from ever disagreeing about what is usable.
|
|
14
|
+
#
|
|
15
|
+
# Signed in only, unlike the rest of the cart surface. What comes
|
|
16
|
+
# back is not the cart's own data but the books behind it — every
|
|
17
|
+
# address the buyer has saved, and their company's sites. A guest
|
|
18
|
+
# token says the caller holds this cart, which it may well do
|
|
19
|
+
# because the account holder adopted a cart the attacker started;
|
|
20
|
+
# it says nothing about the account. A genuine guest cart has
|
|
21
|
+
# neither book, so requiring the account costs nothing but the 401.
|
|
22
|
+
class AddressesController < Store::ResourceController
|
|
23
|
+
include Spree::Api::V3::CartResolvable
|
|
24
|
+
|
|
25
|
+
prepend_before_action :require_authentication!
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def set_parent
|
|
30
|
+
@parent = @cart = find_cart(token: nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def model_class
|
|
34
|
+
Spree::Address
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def serializer_class
|
|
38
|
+
Spree.api.address_serializer
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def collection_includes
|
|
42
|
+
[:owner]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parent_association
|
|
46
|
+
:selectable_addresses
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -8,7 +8,7 @@ module Spree
|
|
|
8
8
|
include Spree::Api::V3::OrderLock
|
|
9
9
|
|
|
10
10
|
before_action :require_authentication!
|
|
11
|
-
before_action :
|
|
11
|
+
before_action :find_owned_cart!
|
|
12
12
|
before_action :stringify_numeric_amount, only: :create
|
|
13
13
|
|
|
14
14
|
request_params 'ApplyStoreCreditParams', action: :create do |body|
|
|
@@ -50,6 +50,14 @@ module Spree
|
|
|
50
50
|
|
|
51
51
|
private
|
|
52
52
|
|
|
53
|
+
# The balance spent here is the cart owner's, so a guest token —
|
|
54
|
+
# which only proves the caller holds this cart — is not enough:
|
|
55
|
+
# the signed-in customer must be the owner.
|
|
56
|
+
def find_owned_cart!
|
|
57
|
+
@cart = resolve_cart(include_completed: false)
|
|
58
|
+
authorize_storefront_write!(@cart, token: nil)
|
|
59
|
+
end
|
|
60
|
+
|
|
53
61
|
# 5.6 storefronts sent a JSON number; read it through its text so no
|
|
54
62
|
# float reaches the money math.
|
|
55
63
|
def stringify_numeric_amount
|
|
@@ -13,7 +13,7 @@ module Spree
|
|
|
13
13
|
|
|
14
14
|
CART_ADDRESS = Spree::Api::V3::RequestSchema.shape('CartAddressParams') do |schema|
|
|
15
15
|
schema.attribute :id, :id,
|
|
16
|
-
description:
|
|
16
|
+
description: "An existing address from the buyer's address book, or from the book of the company the cart is for. The other fields are then ignored."
|
|
17
17
|
schema.include_attributes Spree::Api::V3::Store::AddressRequestSchema
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -25,10 +25,10 @@ module Spree
|
|
|
25
25
|
schema.attribute :currency, :currency
|
|
26
26
|
schema.attribute :locale, :locale
|
|
27
27
|
schema.attribute :shipping_address_id, :id,
|
|
28
|
-
description:
|
|
28
|
+
description: "An existing address to ship to, from the buyer's address book or from the book of the company the cart is for. List both with `GET /store/carts/:id/addresses`.",
|
|
29
29
|
example: 'addr_abc123'
|
|
30
30
|
schema.attribute :billing_address_id, :id,
|
|
31
|
-
description:
|
|
31
|
+
description: "An existing address to bill, from the buyer's address book or from the book of the company the cart is for. List both with `GET /store/carts/:id/addresses`.",
|
|
32
32
|
example: 'addr_def456'
|
|
33
33
|
schema.attribute :preferred_stock_location_id, :id
|
|
34
34
|
schema.attribute :company_id, :id, nullable: true, description: <<~TEXT.squish
|
|
@@ -112,6 +112,7 @@ module Spree
|
|
|
112
112
|
# Auto-advances to the next checkout step when possible.
|
|
113
113
|
def update
|
|
114
114
|
find_cart!
|
|
115
|
+
authorize_address_book_access!
|
|
115
116
|
|
|
116
117
|
with_order_lock do
|
|
117
118
|
result = Spree.carts_update_service.call(
|
|
@@ -238,6 +239,19 @@ module Spree
|
|
|
238
239
|
|
|
239
240
|
# Find incomplete cart for associate action.
|
|
240
241
|
# Only finds guest carts (no user) or carts already owned by current user (idempotent).
|
|
242
|
+
ADDRESS_BOOK_PARAMS = %i[shipping_address billing_address shipping_address_id billing_address_id].freeze
|
|
243
|
+
|
|
244
|
+
# A typed address on a customer's cart is saved into that customer's
|
|
245
|
+
# address book and may become their default, and an address id names
|
|
246
|
+
# an entry of that book. The guest token proves the caller holds the
|
|
247
|
+
# cart, not the account, so those writes need the owner.
|
|
248
|
+
def authorize_address_book_access!
|
|
249
|
+
return if @cart.customer.nil?
|
|
250
|
+
return unless ADDRESS_BOOK_PARAMS.any? { |key| params.key?(key) }
|
|
251
|
+
|
|
252
|
+
authorize_storefront_write!(@cart, token: nil)
|
|
253
|
+
end
|
|
254
|
+
|
|
241
255
|
def find_cart_for_association
|
|
242
256
|
current_store.carts.incomplete.where(customer: [nil, current_user]).find_by_prefix_id!(params[:id])
|
|
243
257
|
end
|
|
@@ -69,6 +69,10 @@ module Spree
|
|
|
69
69
|
Spree.api.address_serializer
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
def collection_includes
|
|
73
|
+
[:owner]
|
|
74
|
+
end
|
|
75
|
+
|
|
72
76
|
# Reading a book and keeping one are different rights: the listing
|
|
73
77
|
# includes what this node inherits, writes stay with the node that
|
|
74
78
|
# owns the row. The Store API authorizes purely by what this scope
|
|
@@ -10,14 +10,30 @@ module Spree
|
|
|
10
10
|
# irreversible, and an unattended session should not be enough to
|
|
11
11
|
# trigger it — the same bar the account already applies to changing
|
|
12
12
|
# an email address.
|
|
13
|
+
#
|
|
14
|
+
# Nothing happens until the request is confirmed from the link emailed
|
|
15
|
+
# to the account's address (see DataRequestConfirmationsController),
|
|
16
|
+
# and the export link is never shown to this session.
|
|
13
17
|
class DataRequestsController < ResourceController
|
|
14
18
|
include Spree::Api::V3::CurrentPasswordConfirmation
|
|
19
|
+
include Spree::Api::V3::AllowedRedirectUrl
|
|
15
20
|
|
|
16
21
|
prepend_before_action :require_authentication!
|
|
17
22
|
|
|
23
|
+
# Each request while one waits sends the confirmation email again.
|
|
24
|
+
rate_limit to: Spree::Api::Config[:rate_limit_password_reset],
|
|
25
|
+
within: Spree::Api::Config[:rate_limit_window].seconds,
|
|
26
|
+
store: Rails.cache,
|
|
27
|
+
only: :create,
|
|
28
|
+
with: RATE_LIMIT_RESPONSE
|
|
29
|
+
|
|
18
30
|
request_params 'CreateDataRequestParams', action: :create do |body|
|
|
19
31
|
body.attribute :kind, :string, choices: Spree::DataRequest::KINDS, description: 'Defaults to `access`.'
|
|
20
32
|
body.attribute :current_password, :string, description: 'Required for `erasure`.'
|
|
33
|
+
body.attribute :redirect_url, :string,
|
|
34
|
+
format: :uri,
|
|
35
|
+
example: 'https://myshop.com/account/data-requests/confirm',
|
|
36
|
+
description: "The storefront page the confirmation link opens, with the token appended. Ignored unless it matches one of the store's allowed origins."
|
|
21
37
|
end
|
|
22
38
|
|
|
23
39
|
# POST /api/v3/store/customers/me/data_requests
|
|
@@ -27,7 +43,9 @@ module Spree
|
|
|
27
43
|
result = Spree::DataRequests::Create.call(
|
|
28
44
|
store: current_store,
|
|
29
45
|
customer: current_user,
|
|
30
|
-
kind: requested_kind
|
|
46
|
+
kind: requested_kind,
|
|
47
|
+
confirm_by_email: true,
|
|
48
|
+
redirect_url: validated_redirect_url
|
|
31
49
|
)
|
|
32
50
|
|
|
33
51
|
return render_result_error(result) unless result.success?
|
|
@@ -6,6 +6,8 @@ module Spree
|
|
|
6
6
|
module Store
|
|
7
7
|
module Customer
|
|
8
8
|
class PasswordResetsController < Store::BaseController
|
|
9
|
+
include Spree::Api::V3::AllowedRedirectUrl
|
|
10
|
+
|
|
9
11
|
allow_guest_storefront_access!
|
|
10
12
|
rate_limit to: Spree::Api::Config[:rate_limit_password_reset],
|
|
11
13
|
within: Spree::Api::Config[:rate_limit_window].seconds,
|
|
@@ -33,26 +35,13 @@ module Spree
|
|
|
33
35
|
|
|
34
36
|
# POST /api/v3/store/password_resets
|
|
35
37
|
def create
|
|
36
|
-
redirect_url = params[:redirect_url]
|
|
37
|
-
|
|
38
|
-
# Validate redirect_url against allowed origins (secure by default).
|
|
39
|
-
# If no allowed origins are configured, redirect_url is silently ignored
|
|
40
|
-
# to prevent open redirect / token exfiltration attacks.
|
|
41
|
-
if redirect_url.present?
|
|
42
|
-
if current_store.allowed_origins.exists? && current_store.allowed_origin?(redirect_url)
|
|
43
|
-
# redirect_url is valid — keep it
|
|
44
|
-
else
|
|
45
|
-
redirect_url = nil
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
38
|
user = Spree.customer_class.find_by(email: params[:email])
|
|
50
39
|
|
|
51
40
|
if user
|
|
52
41
|
event_name = 'customer.password_reset_requested'
|
|
53
42
|
payload = user.event_payload_for(
|
|
54
43
|
event_name,
|
|
55
|
-
reset_token: user.generate_token_for(:password_reset), store: current_store, redirect_url:
|
|
44
|
+
reset_token: user.generate_token_for(:password_reset), store: current_store, redirect_url: validated_redirect_url
|
|
56
45
|
)
|
|
57
46
|
user.publish_event(event_name, payload)
|
|
58
47
|
end
|
|
@@ -31,7 +31,8 @@ module Spree
|
|
|
31
31
|
|
|
32
32
|
request_params 'UpdateCustomerParams', action: :update do |body|
|
|
33
33
|
body.attribute :email, :string, format: :email, example: 'customer@example.com'
|
|
34
|
-
body.attribute :password, :string
|
|
34
|
+
body.attribute :password, :string,
|
|
35
|
+
description: 'Changing it signs the customer out everywhere: every refresh token is revoked, so sign in again once the access token expires.'
|
|
35
36
|
body.attribute :password_confirmation, :string
|
|
36
37
|
body.attribute :current_password, :string,
|
|
37
38
|
description: 'Required when changing the email or the password.'
|
|
@@ -75,6 +76,7 @@ module Spree
|
|
|
75
76
|
|
|
76
77
|
# GET /api/v3/store/customer
|
|
77
78
|
def show
|
|
79
|
+
preload_default_addresses
|
|
78
80
|
render json: serialize_resource(current_user)
|
|
79
81
|
end
|
|
80
82
|
|
|
@@ -86,6 +88,10 @@ module Spree
|
|
|
86
88
|
current_user.consent_source = Spree::ConsentRecord::ACCOUNT
|
|
87
89
|
|
|
88
90
|
if current_user.update(update_params)
|
|
91
|
+
# A password change is how a customer takes a stolen session
|
|
92
|
+
# back, so no refresh token issued before it may outlive it —
|
|
93
|
+
# the reset flow does the same.
|
|
94
|
+
Spree::RefreshToken.revoke_all_for(current_user) if update_params[:password].present?
|
|
89
95
|
render json: serialize_resource(current_user)
|
|
90
96
|
else
|
|
91
97
|
render_errors(current_user.errors)
|
|
@@ -115,6 +121,12 @@ module Spree
|
|
|
115
121
|
|
|
116
122
|
private
|
|
117
123
|
|
|
124
|
+
# Both default addresses live in one table, so they are fetched in one
|
|
125
|
+
# query rather than one each.
|
|
126
|
+
def preload_default_addresses
|
|
127
|
+
ActiveRecord::Associations::Preloader.new(records: [current_user], associations: %i[bill_address ship_address]).call
|
|
128
|
+
end
|
|
129
|
+
|
|
118
130
|
def sensitive_update?
|
|
119
131
|
(params[:email].present? && params[:email] != current_user.email) ||
|
|
120
132
|
params[:password].present?
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Store
|
|
5
|
+
# Confirms a request a customer raised, from the page the emailed link
|
|
6
|
+
# opens. The page calls this when the person presses its button, never
|
|
7
|
+
# on load: some mail scanners open links and run the page's scripts.
|
|
8
|
+
class DataRequestConfirmationsController < DataRequestsController
|
|
9
|
+
# POST /api/v3/store/data_requests/:token/confirmation
|
|
10
|
+
def create
|
|
11
|
+
return render_confirmation_invalid(status: :unprocessable_content) unless data_request
|
|
12
|
+
|
|
13
|
+
result = Spree::DataRequests::Confirm.call(data_request: data_request)
|
|
14
|
+
|
|
15
|
+
return render_confirmation_invalid(status: :unprocessable_content) unless result.success?
|
|
16
|
+
|
|
17
|
+
render json: serialize_resource(result.value)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Store
|
|
5
|
+
# A data request as seen by whoever holds its confirmation token: the
|
|
6
|
+
# person reading mail at the address the request names.
|
|
7
|
+
#
|
|
8
|
+
# Token-addressed rather than session-authenticated, because the link
|
|
9
|
+
# arrives by email and may be opened on a device that was never signed
|
|
10
|
+
# in. Besides the export email, it is the only place the export link is
|
|
11
|
+
# shown, since the token is what reached the address the request names.
|
|
12
|
+
class DataRequestsController < Store::BaseController
|
|
13
|
+
allow_guest_storefront_access!
|
|
14
|
+
|
|
15
|
+
# The response can carry a live link to a person's whole export, the
|
|
16
|
+
# same credential the download itself keeps out of shared caches.
|
|
17
|
+
before_action { response.headers['Cache-Control'] = 'no-store' }
|
|
18
|
+
|
|
19
|
+
# GET /api/v3/store/data_requests/:token
|
|
20
|
+
def show
|
|
21
|
+
return render_confirmation_invalid(status: :not_found) unless data_request
|
|
22
|
+
|
|
23
|
+
render json: serialize_resource(data_request)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def serializer_class
|
|
29
|
+
Spree.api.data_request_serializer
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def serializer_params
|
|
33
|
+
super.merge(download_url_visible: true)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def data_request
|
|
39
|
+
return @data_request if defined?(@data_request)
|
|
40
|
+
|
|
41
|
+
token = params[:token].presence
|
|
42
|
+
@data_request = token && Spree::DataRequest.for_store(current_store).find_by_token_for(:confirmation, token)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def render_confirmation_invalid(status:)
|
|
46
|
+
render_error(
|
|
47
|
+
code: ERROR_CODES[:invalid_token],
|
|
48
|
+
message: I18n.t('spree.data_request_errors.confirmation_invalid'),
|
|
49
|
+
status: status
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -93,7 +93,7 @@ module Spree
|
|
|
93
93
|
def covers_cart?(location, cart)
|
|
94
94
|
return true if location.pickup_stock_policy == 'any'
|
|
95
95
|
|
|
96
|
-
cart
|
|
96
|
+
pickup_line_items(cart).all? do |line_item|
|
|
97
97
|
next true if line_item.variant.digital?
|
|
98
98
|
next true unless line_item.variant.should_track_inventory?
|
|
99
99
|
|
|
@@ -101,6 +101,11 @@ module Spree
|
|
|
101
101
|
stock_level.present? && stock_level.available_count >= line_item.quantity
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
|
+
|
|
105
|
+
# Loaded once for every location the cart is checked against.
|
|
106
|
+
def pickup_line_items(cart)
|
|
107
|
+
@pickup_line_items ||= cart.line_items.includes(variant: [:delivery_profile, { product: %i[delivery_profile store] }]).to_a
|
|
108
|
+
end
|
|
104
109
|
end
|
|
105
110
|
end
|
|
106
111
|
end
|
|
@@ -3,6 +3,8 @@ module Spree
|
|
|
3
3
|
module V3
|
|
4
4
|
module Store
|
|
5
5
|
class NewsletterSubscribersController < Store::BaseController
|
|
6
|
+
include Spree::Api::V3::AllowedRedirectUrl
|
|
7
|
+
|
|
6
8
|
allow_guest_storefront_access!
|
|
7
9
|
rate_limit to: Spree::Api::Config[:rate_limit_register],
|
|
8
10
|
within: Spree::Api::Config[:rate_limit_window].seconds,
|
|
@@ -139,17 +141,6 @@ module Spree
|
|
|
139
141
|
subscriber = Spree::NewsletterSubscriber.for_store(current_store).find_by_token_for(:unsubscribe, params[:token])
|
|
140
142
|
subscriber if subscriber&.prefixed_id == params[:id]
|
|
141
143
|
end
|
|
142
|
-
|
|
143
|
-
# Drop redirect_url when it isn't in the store's allow-list — secure-by-default,
|
|
144
|
-
# mirrors password_resets. Returning nil omits it from the webhook payload rather
|
|
145
|
-
# than rejecting the request, so callers can't probe the allow-list via 4xx errors.
|
|
146
|
-
def validated_redirect_url
|
|
147
|
-
redirect_url = params[:redirect_url]
|
|
148
|
-
return nil if redirect_url.blank?
|
|
149
|
-
return nil unless current_store.allowed_origin?(redirect_url)
|
|
150
|
-
|
|
151
|
-
redirect_url
|
|
152
|
-
end
|
|
153
144
|
end
|
|
154
145
|
end
|
|
155
146
|
end
|
|
@@ -5,6 +5,7 @@ module Spree
|
|
|
5
5
|
module Products
|
|
6
6
|
class FiltersController < Store::BaseController
|
|
7
7
|
include Spree::Api::V3::Store::SearchProviderSupport
|
|
8
|
+
include Spree::Api::V3::Store::StorefrontProducts
|
|
8
9
|
|
|
9
10
|
def index
|
|
10
11
|
json = Rails.cache.fetch(filters_cache_key, expires_in: 15.minutes) do
|
|
@@ -30,8 +31,8 @@ module Spree
|
|
|
30
31
|
|
|
31
32
|
def filters_cache_key
|
|
32
33
|
products_table = Spree::Product.table_name
|
|
33
|
-
stats =
|
|
34
|
-
|
|
34
|
+
stats = storefront_products.
|
|
35
|
+
pick(Arel.sql("MAX(#{products_table}.updated_at)"), Arel.sql("COUNT(DISTINCT #{products_table}.id)"))
|
|
35
36
|
max_updated = stats&.first&.to_i
|
|
36
37
|
product_count = stats&.last || 0
|
|
37
38
|
|
|
@@ -41,6 +42,7 @@ module Spree
|
|
|
41
42
|
current_currency,
|
|
42
43
|
current_locale,
|
|
43
44
|
(hide_prices? ? 'prices_hidden' : nil),
|
|
45
|
+
buyer_catalog_fragment,
|
|
44
46
|
category&.cache_key_with_version,
|
|
45
47
|
collection&.cache_key_with_version,
|
|
46
48
|
search_query,
|
|
@@ -53,11 +55,24 @@ module Spree
|
|
|
53
55
|
parts.compact.join('/')
|
|
54
56
|
end
|
|
55
57
|
|
|
58
|
+
# The buyer's catalogs partition the cache: a facet payload built
|
|
59
|
+
# for one assortment must not be served to a buyer who sees
|
|
60
|
+
# another, or it would describe products their listing never
|
|
61
|
+
# returns.
|
|
62
|
+
def buyer_catalog_fragment
|
|
63
|
+
catalogs = Spree::Catalog.for_buyer(store: current_store, customer: current_user, channel: current_channel)
|
|
64
|
+
return if catalogs.empty?
|
|
65
|
+
|
|
66
|
+
# Versioned, since adding or removing a product touches the
|
|
67
|
+
# catalog without touching the product count or timestamps.
|
|
68
|
+
catalogs.map(&:cache_key_with_version).sort.join('/')
|
|
69
|
+
end
|
|
70
|
+
|
|
56
71
|
def filters_scope
|
|
57
72
|
# Mirror ProductsController#scope so facet counts describe the
|
|
58
73
|
# same product set the listing returns (coming-soon pre-orders
|
|
59
|
-
# included).
|
|
60
|
-
scope =
|
|
74
|
+
# included) — narrowed to the buyer's catalogs like the listing.
|
|
75
|
+
scope = storefront_products
|
|
61
76
|
scope = scope.in_category(category) if category.present?
|
|
62
77
|
scope = scope.in_collection(collection) if collection.present?
|
|
63
78
|
scope
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# The shared variant attributes, plus the marketplace configuration
|
|
6
|
+
# only an operator sets.
|
|
7
|
+
class VariantRequestSchema < Spree::Api::V3::VariantRequestSchema
|
|
8
|
+
publishes 'VariantParams'
|
|
9
|
+
|
|
10
|
+
attribute :seller_id, :id
|
|
11
|
+
attribute :delivery_profile_id, :id, nullable: true
|
|
12
|
+
attribute :tax_category_id, :id
|
|
13
|
+
attribute :prices, :array, of: Spree::Api::V3::PriceRequestSchema,
|
|
14
|
+
description: 'Per-currency prices. Upserted by currency.'
|
|
15
|
+
attribute :stock_levels, :array, of: Spree::Api::V3::StockLevelRequestSchema,
|
|
16
|
+
description: 'Stock per location. Upserted by location; omitted locations are removed.'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
# What a downloadable file on a product carries, whichever panel writes
|
|
5
|
+
# it. The operator's endpoint adds the provider fields on top.
|
|
6
|
+
class DigitalAssetRequestSchema < Spree::Api::V3::RequestSchema
|
|
7
|
+
# Names a blob rather than a column, so it is attached separately.
|
|
8
|
+
attribute :signed_id, :string,
|
|
9
|
+
permit: false,
|
|
10
|
+
description: "A `signed_id` from `files.upload()` with `visibility: 'private'`. On update it replaces the file for everyone who already bought it. A seller's file must be of a content type the marketplace accepts, checked against the file's own signature where it has one."
|
|
11
|
+
# Resolved through the parent rather than assigned, so an asset can't
|
|
12
|
+
# be pointed at a variant of another product or store.
|
|
13
|
+
attribute :variant_id, :id,
|
|
14
|
+
permit: false,
|
|
15
|
+
description: "On create, defaults to the product's default variant when omitted. On update, omitting it keeps the current variant."
|
|
16
|
+
attribute :authorized_clicks, :integer,
|
|
17
|
+
nullable: true,
|
|
18
|
+
description: "Null means the store's download settings apply."
|
|
19
|
+
attribute :authorized_days, :integer,
|
|
20
|
+
nullable: true,
|
|
21
|
+
description: "Null means the store's download settings apply."
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Seller
|
|
5
|
+
# The operator's price shape, except `currency` may be left out: the
|
|
6
|
+
# store's currency is assumed (see `default_price_currencies`).
|
|
7
|
+
class PriceRequestSchema < Spree::Api::V3::RequestSchema
|
|
8
|
+
attribute :currency, :currency,
|
|
9
|
+
example: 'USD',
|
|
10
|
+
description: "Omit to price in the store's currency. A seller has no currency of their own."
|
|
11
|
+
attribute :amount, :decimal,
|
|
12
|
+
required: true,
|
|
13
|
+
example: '24.00',
|
|
14
|
+
description: 'A canonical decimal string (`"1234.56"`: period decimal, no grouping). The API does not parse localized input.'
|
|
15
|
+
attribute :compare_at_amount, :decimal, nullable: true, example: '30.00'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Seller
|
|
5
|
+
# The shared variant attributes. Narrower than the operator's set:
|
|
6
|
+
# `seller_id` would let a payload hand a variant to somebody else, and
|
|
7
|
+
# `tax_category_id`/`delivery_profile_id` are marketplace configuration.
|
|
8
|
+
class VariantRequestSchema < Spree::Api::V3::VariantRequestSchema
|
|
9
|
+
publishes 'VariantParams'
|
|
10
|
+
|
|
11
|
+
attribute :prices, :array, of: Spree::Api::V3::Seller::PriceRequestSchema,
|
|
12
|
+
description: 'Per-currency prices. Upserted by currency.'
|
|
13
|
+
attribute :stock_levels, :array, of: Spree::Api::V3::StockLevelRequestSchema,
|
|
14
|
+
description: "Stock per location, at this seller's own locations only: a location belonging to anyone else is ignored."
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Api
|
|
3
3
|
module V3
|
|
4
|
-
# A variant's own attributes,
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# A variant's own attributes, the ones a seller may write too. Each API
|
|
5
|
+
# publishes its own `VariantParams` from a subclass, which the variant
|
|
6
|
+
# endpoints and the variants nested in a product payload both extend.
|
|
7
7
|
class VariantRequestSchema < Spree::Api::V3::RequestSchema
|
|
8
8
|
attribute :sku, :string
|
|
9
9
|
attribute :barcode, :string
|
|
@@ -13,14 +13,14 @@ module Spree
|
|
|
13
13
|
attribute :height, :number, nullable: true
|
|
14
14
|
attribute :width, :number, nullable: true
|
|
15
15
|
attribute :depth, :number, nullable: true
|
|
16
|
-
attribute :weight_unit, :
|
|
17
|
-
attribute :dimensions_unit, :
|
|
16
|
+
attribute :weight_unit, :weight_unit, nullable: true, description: "Null falls back to the store's weight unit."
|
|
17
|
+
attribute :dimensions_unit, :dimension_unit, nullable: true, description: "Null falls back to the store's unit system."
|
|
18
18
|
attribute :hs_code, :string
|
|
19
19
|
attribute :customs_description, :string
|
|
20
20
|
attribute :country_of_origin, :country
|
|
21
21
|
attribute :minimum_order_quantity, :integer, nullable: true
|
|
22
22
|
attribute :order_multiple, :integer, nullable: true
|
|
23
|
-
attribute :purchase_unit, :string
|
|
23
|
+
attribute :purchase_unit, :string, nullable: true, choices: Spree::Variant::PURCHASE_UNITS
|
|
24
24
|
attribute :units_per_carton, :integer, nullable: true, description: 'How many units fill one carton.'
|
|
25
25
|
attribute :carton_package_type_id, :id,
|
|
26
26
|
nullable: true,
|
|
@@ -29,8 +29,6 @@ module Spree
|
|
|
29
29
|
nullable: true,
|
|
30
30
|
description: "What one packed carton weighs, in the variant's weight unit."
|
|
31
31
|
attribute :cartons_per_pallet, :integer, nullable: true, description: 'How many cartons stack on one pallet.'
|
|
32
|
-
attribute :seller_id, :id
|
|
33
|
-
attribute :delivery_profile_id, :id, nullable: true
|
|
34
32
|
attribute :track_inventory, :boolean
|
|
35
33
|
attribute :preorderable, :boolean,
|
|
36
34
|
description: "Sell this variant before it's in stock. Adds the pre-order label and ships-by promise; the cap comes from `backorder_limit`, an incoming stock count, or backorder."
|
|
@@ -40,12 +38,9 @@ module Spree
|
|
|
40
38
|
attribute :backorder_limit, :integer,
|
|
41
39
|
nullable: true,
|
|
42
40
|
description: 'Maximum units sellable beyond available stock, capping backorders and pre-orders. Null is unlimited.'
|
|
43
|
-
attribute :tax_category_id, :id
|
|
44
41
|
attribute :position, :integer
|
|
45
|
-
attribute :
|
|
46
|
-
description: '
|
|
47
|
-
attribute :stock_levels, :array, of: Spree::Api::V3::StockLevelRequestSchema,
|
|
48
|
-
description: 'Stock per location. Upserted by location; omitted locations are removed.'
|
|
42
|
+
attribute :options, :array, of: Spree::Api::V3::VariantOptionRequestSchema,
|
|
43
|
+
description: 'One pair per option type the variant takes part in (size, color). Option types and values are created when missing.'
|
|
49
44
|
end
|
|
50
45
|
end
|
|
51
46
|
end
|