spree_api 6.0.0.rc3 → 6.0.0.rc4
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/email_template_lookup.rb +6 -1
- data/app/controllers/concerns/spree/api/v3/admin/reporting_authorization.rb +17 -8
- data/app/controllers/concerns/spree/api/v3/admin/store_context.rb +7 -0
- data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +61 -0
- data/app/controllers/concerns/spree/api/v3/error_handler.rb +49 -0
- data/app/controllers/concerns/spree/api/v3/params_normalizer.rb +76 -7
- data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +60 -3
- data/app/controllers/spree/api/v3/admin/companies/tax_exemption_certificates_controller.rb +11 -5
- data/app/controllers/spree/api/v3/admin/concerns/external_references.rb +3 -0
- data/app/controllers/spree/api/v3/admin/exports_controller.rb +15 -3
- data/app/controllers/spree/api/v3/admin/imports_controller.rb +12 -3
- data/app/controllers/spree/api/v3/admin/markets_controller.rb +3 -1
- data/app/controllers/spree/api/v3/admin/me_controller.rb +21 -1
- data/app/controllers/spree/api/v3/admin/oauth/application_tokens_controller.rb +56 -0
- data/app/controllers/spree/api/v3/admin/oauth/applications_controller.rb +55 -0
- data/app/controllers/spree/api/v3/admin/oauth/authorizations_controller.rb +294 -0
- data/app/controllers/spree/api/v3/admin/order_groups_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +8 -0
- data/app/controllers/spree/api/v3/admin/orders/items_controller.rb +1 -0
- data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +7 -0
- data/app/controllers/spree/api/v3/admin/orders_controller.rb +9 -2
- data/app/controllers/spree/api/v3/admin/purchase_orders_controller.rb +1 -0
- data/app/controllers/spree/api/v3/admin/reporting_controller.rb +20 -14
- data/app/controllers/spree/api/v3/admin/stock_levels_controller.rb +1 -1
- data/app/controllers/spree/api/v3/admin/tags_controller.rb +8 -5
- data/app/controllers/spree/api/v3/admin/tax_exemption_reasons_controller.rb +58 -0
- data/app/controllers/spree/api/v3/admin/tax_rates_controller.rb +0 -1
- data/app/controllers/spree/api/v3/admin/translations/batches_controller.rb +17 -11
- data/app/controllers/spree/api/v3/oauth/authorizations_controller.rb +61 -0
- data/app/controllers/spree/api/v3/oauth/metadata_controller.rb +57 -0
- data/app/controllers/spree/api/v3/oauth/server_metadata_controller.rb +57 -0
- data/app/controllers/spree/api/v3/resource_controller.rb +1 -0
- data/app/controllers/spree/api/v3/store/carts_controller.rb +4 -5
- data/app/request_schemas/spree/api/v3/admin/email_template_preview_request_schema.rb +4 -0
- data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +2 -0
- data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +2 -0
- data/app/serializers/spree/api/v3/admin/oauth_access_token_serializer.rb +62 -0
- data/app/serializers/spree/api/v3/admin/oauth_application_serializer.rb +56 -0
- data/app/serializers/spree/api/v3/admin/oauth_consent_request_serializer.rb +25 -0
- data/app/serializers/spree/api/v3/admin/oauth_protected_resource_serializer.rb +27 -0
- data/app/serializers/spree/api/v3/admin/oauth_redirect_serializer.rb +17 -0
- data/app/serializers/spree/api/v3/admin/payment_capture_event_serializer.rb +16 -0
- data/app/serializers/spree/api/v3/admin/payment_serializer.rb +12 -1
- data/app/serializers/spree/api/v3/admin/return_serializer.rb +2 -0
- data/app/serializers/spree/api/v3/admin/tax_exemption_certificate_serializer.rb +7 -3
- data/app/serializers/spree/api/v3/admin/tax_exemption_reason_serializer.rb +20 -0
- data/app/serializers/spree/api/v3/admin/tax_rate_serializer.rb +1 -2
- data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +4 -1
- data/app/serializers/spree/api/v3/base_serializer.rb +14 -0
- data/config/routes.rb +61 -1
- data/lib/spree/api/agent_membership_map.rb +73 -0
- data/lib/spree/api/agent_resource_map.rb +350 -0
- data/lib/spree/api/agent_workflow_endpoints.rb +113 -0
- data/lib/spree/api/agent_write_schemas.rb +119 -0
- data/lib/spree/api/dependencies.rb +4 -3
- data/lib/spree/api/engine.rb +40 -0
- data/lib/spree/api/extension_fields.rb +167 -0
- data/lib/spree/api/oauth.rb +243 -0
- data/lib/spree/api.rb +8 -0
- data/lib/typelizer/open_enums.rb +17 -3
- metadata +26 -6
|
@@ -20,9 +20,10 @@ module Spree
|
|
|
20
20
|
# authorization, and rendering.
|
|
21
21
|
class BatchesController < Admin::BaseController
|
|
22
22
|
# The batch spans heterogeneous resource types, so a single static
|
|
23
|
-
# scope can't gate it. We verify the
|
|
24
|
-
# write_<resource>
|
|
25
|
-
# which is stricter than the static check
|
|
23
|
+
# scope can't gate it. We verify the caller holds the matching
|
|
24
|
+
# write_<resource> permission for EVERY entry (see
|
|
25
|
+
# require_batch_scopes!), which is stricter than the static check
|
|
26
|
+
# and keeps the audit model.
|
|
26
27
|
skip_scope_check!
|
|
27
28
|
|
|
28
29
|
request_params 'TranslationBatchParams' do |body|
|
|
@@ -63,19 +64,24 @@ module Spree
|
|
|
63
64
|
|
|
64
65
|
private
|
|
65
66
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
67
|
+
# Require write_<resource> for every distinct resource type in the
|
|
68
|
+
# batch.
|
|
69
|
+
#
|
|
70
|
+
# Asked of the caller rather than of a secret key, because there
|
|
71
|
+
# are three principals and only one of them carries scopes on a
|
|
72
|
+
# key. An OAuth token resolves as a staff user with no key, so
|
|
73
|
+
# reading `current_api_key` here waved it through and the ceiling
|
|
74
|
+
# the merchant consented to went unenforced on the one write this
|
|
75
|
+
# controller performs — `holds_permission?` answers for all three.
|
|
76
|
+
#
|
|
77
|
+
# Returns false (and renders 403) when a permission is missing.
|
|
70
78
|
def require_batch_scopes!(batch)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
missing = batch.required_scopes.reject { |scope| current_api_key.has_scope?(scope) }
|
|
79
|
+
missing = batch.required_scopes.reject { |scope| holds_permission?(scope) }
|
|
74
80
|
return true if missing.empty?
|
|
75
81
|
|
|
76
82
|
render_error(
|
|
77
83
|
code: ERROR_CODES[:access_denied],
|
|
78
|
-
message: "
|
|
84
|
+
message: "You do not have permission to #{missing.map { |key| key.tr('_', ' ') }.to_sentence}.",
|
|
79
85
|
status: :forbidden,
|
|
80
86
|
details: { required_scopes: missing }
|
|
81
87
|
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Oauth
|
|
5
|
+
# The authorization endpoint a client sends the browser to.
|
|
6
|
+
#
|
|
7
|
+
# It renders nothing itself: consent belongs in the dashboard, where
|
|
8
|
+
# the merchant is already signed in and the screen matches the rest of
|
|
9
|
+
# the admin. This hands the request over with its parameters intact,
|
|
10
|
+
# and the dashboard reads them back through the JSON endpoints under
|
|
11
|
+
# /api/v3/admin/oauth.
|
|
12
|
+
#
|
|
13
|
+
# It is a real route rather than a skipped one because RFC 8414
|
|
14
|
+
# requires the metadata document to name an `authorization_endpoint`,
|
|
15
|
+
# and a consumer client refuses a document that advertises the
|
|
16
|
+
# authorization-code flow without one.
|
|
17
|
+
class AuthorizationsController < ActionController::Base
|
|
18
|
+
include Spree::Core::ControllerHelpers::Store
|
|
19
|
+
|
|
20
|
+
# The authorization endpoint is reached by a cross-site GET on
|
|
21
|
+
# purpose — a client sends the browser here — and forgery
|
|
22
|
+
# protection never applies to a GET, so `:exception` costs nothing
|
|
23
|
+
# while leaving the endpoint reachable. Nothing is granted here
|
|
24
|
+
# either way: the action only redirects, and consent is submitted
|
|
25
|
+
# to the Admin API under the merchant's own session.
|
|
26
|
+
protect_from_forgery with: :exception
|
|
27
|
+
|
|
28
|
+
# Carried across to the dashboard. Everything else is dropped, so a
|
|
29
|
+
# crafted link cannot smuggle extra parameters into the consent page.
|
|
30
|
+
FORWARDED_PARAMETERS = %w[
|
|
31
|
+
client_id redirect_uri response_type scope state
|
|
32
|
+
code_challenge code_challenge_method resource
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
def new
|
|
36
|
+
redirect_to consent_url, allow_other_host: true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# The client id names the store, not the hostname. An installation
|
|
42
|
+
# serving several stores from one API origin would otherwise send
|
|
43
|
+
# every consent to whichever store that host happens to resolve as,
|
|
44
|
+
# and the merchant would approve on the wrong one.
|
|
45
|
+
def consent_url
|
|
46
|
+
query = params.permit(*FORWARDED_PARAMETERS).to_h.compact_blank.to_query
|
|
47
|
+
base = Spree::Stores::DashboardUrl.call(store: consent_store)
|
|
48
|
+
|
|
49
|
+
"#{base}/#{consent_store.prefixed_id}/oauth/authorize?#{query}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Spree::Store]
|
|
53
|
+
def consent_store
|
|
54
|
+
@consent_store ||=
|
|
55
|
+
Spree::OauthApplication.find_by(uid: params[:client_id])&.store || current_store
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Oauth
|
|
5
|
+
# RFC 9728 Protected Resource Metadata — the one discovery document
|
|
6
|
+
# Doorkeeper does not ship (it serves RFC 8414 authorization-server
|
|
7
|
+
# metadata itself).
|
|
8
|
+
#
|
|
9
|
+
# A client reads this to learn which authorization server guards a
|
|
10
|
+
# resource. It is deliberately unauthenticated: a client has no
|
|
11
|
+
# credential at the point it asks.
|
|
12
|
+
class MetadataController < ActionController::API
|
|
13
|
+
|
|
14
|
+
# A public discovery document, read before any credential exists.
|
|
15
|
+
# The module has to be included because `ActionController::API`
|
|
16
|
+
# carries none of it; `:exception` is free here because the only
|
|
17
|
+
# action is a GET, which forgery protection never applies to.
|
|
18
|
+
include ActionController::RequestForgeryProtection
|
|
19
|
+
protect_from_forgery with: :exception
|
|
20
|
+
|
|
21
|
+
# The `resource` value must equal the URL the client was pointed at,
|
|
22
|
+
# path and all, or a consumer client refuses to continue — so it is
|
|
23
|
+
# built from this request's own origin rather than from anything
|
|
24
|
+
# configured elsewhere.
|
|
25
|
+
def show
|
|
26
|
+
resource = Spree::Api::Oauth.resource_identifier(params[:resource_key], request.base_url)
|
|
27
|
+
return head :not_found if resource.blank?
|
|
28
|
+
|
|
29
|
+
document = Spree::OauthProtectedResource.new(
|
|
30
|
+
resource: resource,
|
|
31
|
+
authorization_servers: [issuer],
|
|
32
|
+
# Exactly what the authorization server will accept.
|
|
33
|
+
#
|
|
34
|
+
# The spec tells a client with no scope challenge to request
|
|
35
|
+
# everything advertised here, and Doorkeeper refuses a request
|
|
36
|
+
# naming a scope it does not know — so advertising one more
|
|
37
|
+
# than it accepts turns every connection into "The requested
|
|
38
|
+
# scope is invalid".
|
|
39
|
+
scopes_supported: Spree::Api::Oauth.staff_scope_keys,
|
|
40
|
+
resource_documentation: 'https://spreecommerce.org/docs/developer/mcp/overview'
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
render json: Spree.api.admin_oauth_protected_resource_serializer.new(document).to_h
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# The authorization server's own origin. This request reached it, so
|
|
49
|
+
# the origin it arrived on is the one a client can use.
|
|
50
|
+
def issuer
|
|
51
|
+
::Doorkeeper.config.issuer.presence || request.base_url
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Oauth
|
|
5
|
+
# RFC 8414 Authorization Server Metadata.
|
|
6
|
+
#
|
|
7
|
+
# The document itself is entirely Doorkeeper's. Only URL generation is
|
|
8
|
+
# Spree's problem: Doorkeeper asks for each endpoint by controller name
|
|
9
|
+
# anchored at the application root, and these routes are drawn inside
|
|
10
|
+
# an isolated engine, which cannot generate from such a name. The
|
|
11
|
+
# injected resolver answers from the engine's own router instead.
|
|
12
|
+
class ServerMetadataController < ::Doorkeeper::MetadataController
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def metadata_response
|
|
16
|
+
@metadata_response ||= ::Doorkeeper::OAuth::MetadataResponse.new(
|
|
17
|
+
request.base_url,
|
|
18
|
+
->(**arguments) { resolve_endpoint(**arguments) }
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Doorkeeper hands over a controller name anchored at the
|
|
23
|
+
# application root (`/api/v3/…`), while the engine's router knows
|
|
24
|
+
# its own controllers by their engine-qualified names
|
|
25
|
+
# (`spree/api/v3/…`). Doorkeeper's own controllers are already
|
|
26
|
+
# absolute and generate from the application router unchanged.
|
|
27
|
+
def resolve_endpoint(**arguments)
|
|
28
|
+
# Doorkeeper prepends a slash to whatever the route mapping
|
|
29
|
+
# held, so a name configured as "/doorkeeper/tokens" arrives
|
|
30
|
+
# doubled.
|
|
31
|
+
name = arguments[:controller].to_s.sub(%r{\A/+}, '')
|
|
32
|
+
|
|
33
|
+
if name.start_with?('doorkeeper/')
|
|
34
|
+
return Spree::Core::Engine.routes.url_for(
|
|
35
|
+
**arguments.except(:controller),
|
|
36
|
+
controller: "/#{name}",
|
|
37
|
+
only_path: false,
|
|
38
|
+
host: request.host,
|
|
39
|
+
protocol: request.protocol,
|
|
40
|
+
port: request.optional_port
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Spree::Core::Engine.routes.url_for(
|
|
45
|
+
**arguments.except(:controller),
|
|
46
|
+
controller: "spree/#{name}",
|
|
47
|
+
only_path: false,
|
|
48
|
+
host: request.host,
|
|
49
|
+
protocol: request.protocol,
|
|
50
|
+
port: request.optional_port
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -268,6 +268,7 @@ module Spree
|
|
|
268
268
|
preload_associations_lazily.
|
|
269
269
|
ransack(ransack_params, auth_object: ransack_auth_object)
|
|
270
270
|
result = @search.result(distinct: collection_distinct?)
|
|
271
|
+
result = result.with_translation_fallback_order if model_class.include?(Spree::TranslatableResource)
|
|
271
272
|
pagy_options = { limit: limit, page: page }
|
|
272
273
|
result = apply_collection_sort(result)
|
|
273
274
|
@pagy, @collection = pagy(result, **pagy_options)
|
|
@@ -123,7 +123,7 @@ module Spree
|
|
|
123
123
|
sweep_unavailable_coupon_code!
|
|
124
124
|
render_cart
|
|
125
125
|
else
|
|
126
|
-
|
|
126
|
+
render_result_failure(result, code: ERROR_CODES[:validation_error])
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
end
|
|
@@ -181,11 +181,10 @@ module Spree
|
|
|
181
181
|
if result.success?
|
|
182
182
|
@cart = result.value
|
|
183
183
|
render_order
|
|
184
|
+
elsif result.error.to_s.present?
|
|
185
|
+
render_result_failure(result, code: ERROR_CODES[:cart_cannot_complete])
|
|
184
186
|
else
|
|
185
|
-
render_service_error(
|
|
186
|
-
result.error.to_s.presence || 'Could not complete checkout',
|
|
187
|
-
code: ERROR_CODES[:cart_cannot_complete]
|
|
188
|
-
)
|
|
187
|
+
render_service_error('Could not complete checkout', code: ERROR_CODES[:cart_cannot_complete])
|
|
189
188
|
end
|
|
190
189
|
rescue ActiveRecord::RecordNotFound
|
|
191
190
|
# Gateway-race retry with the id the client got back: the webhook
|
|
@@ -25,6 +25,10 @@ module Spree
|
|
|
25
25
|
Spree::Emails::Branding::COLORS.each { |name| branding.attribute name, :string, nullable: true }
|
|
26
26
|
branding.attribute :font, :string, nullable: true, choices: Spree::Emails::Branding::FONTS.keys
|
|
27
27
|
end
|
|
28
|
+
attribute :mailer_logo_signed_id, :string,
|
|
29
|
+
nullable: true,
|
|
30
|
+
permit: false,
|
|
31
|
+
description: 'An unsaved email logo: the signed id of an uploaded image, or null to preview without one.'
|
|
28
32
|
end
|
|
29
33
|
end
|
|
30
34
|
end
|
|
@@ -5,6 +5,8 @@ module Spree
|
|
|
5
5
|
# Admin API Line Item Serializer
|
|
6
6
|
# Extends the store serializer with metadata visibility
|
|
7
7
|
class LineItemSerializer < V3::LineItemSerializer
|
|
8
|
+
include Concerns::ExternalReferencesAttribute
|
|
9
|
+
|
|
8
10
|
without_formatted_money
|
|
9
11
|
|
|
10
12
|
# The Admin API has no guest gating — money fields inherited from the
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# One person's live access through a connected client.
|
|
6
|
+
#
|
|
7
|
+
# The token itself is never serialized: it is a bearer credential, and
|
|
8
|
+
# the screen that lists connections only needs to say whose access it
|
|
9
|
+
# is and what that person approved.
|
|
10
|
+
class OauthAccessTokenSerializer < V3::BaseSerializer
|
|
11
|
+
typelize owner_id: [:string, nullable: true],
|
|
12
|
+
owner_type: [:string, nullable: true],
|
|
13
|
+
scopes: [:string, multi: true],
|
|
14
|
+
expired: :boolean
|
|
15
|
+
|
|
16
|
+
attributes created_at: :iso8601
|
|
17
|
+
|
|
18
|
+
# Who connected, without expanding: the id and the kind, so a
|
|
19
|
+
# client can render a reference or follow it.
|
|
20
|
+
attribute :owner_id do |token|
|
|
21
|
+
token.resource_owner.try(:prefixed_id)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attribute :owner_type do |token|
|
|
25
|
+
Spree::Base.polymorphic_api_type(token.resource_owner_type)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Who connected, as the record itself rather than a summary of it,
|
|
29
|
+
# so the dashboard shows a person the way it does everywhere else:
|
|
30
|
+
# a name over an email, with an avatar.
|
|
31
|
+
#
|
|
32
|
+
# The serializer follows the record's kind, because a grant's owner
|
|
33
|
+
# is polymorphic — an admin today, a customer when a storefront
|
|
34
|
+
# client needs one. Alba hands the proc the object for exactly this.
|
|
35
|
+
one :resource_owner,
|
|
36
|
+
key: :owner,
|
|
37
|
+
resource: proc { |owner| owner_serializer_for(owner) },
|
|
38
|
+
if: proc { expand?('owner') }
|
|
39
|
+
|
|
40
|
+
# @param owner [Object] the grant's resource owner
|
|
41
|
+
# @return [Class] the serializer for that kind of owner
|
|
42
|
+
def self.owner_serializer_for(owner)
|
|
43
|
+
case Spree::Base.polymorphic_api_type(owner.class.name)
|
|
44
|
+
when 'customer' then Spree.api.admin_customer_serializer
|
|
45
|
+
else Spree.api.admin_admin_user_serializer
|
|
46
|
+
end.to_s.constantize
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# What this person approved, as permission keys.
|
|
50
|
+
attribute :scopes do |token|
|
|
51
|
+
token.scopes.to_a
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Whether the current access token has lapsed. The connection is
|
|
55
|
+
# still live — a refresh mints another — but a merchant reading the
|
|
56
|
+
# list deserves to know which agents hold access right now.
|
|
57
|
+
attribute :expired, &:expired?
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin API serializer for {Spree::OauthApplication}.
|
|
6
|
+
#
|
|
7
|
+
# Exposes `client_id` but never `secret`. The id is public by
|
|
8
|
+
# design — it is what a merchant pastes into a connector — while the
|
|
9
|
+
# secret is null here anyway, because every MCP client is a public
|
|
10
|
+
# client that proves itself with PKCE instead.
|
|
11
|
+
#
|
|
12
|
+
# `scopes` and `last_authorized_at` come from the live tokens rather than
|
|
13
|
+
# the registration — a client is registered once and granted per
|
|
14
|
+
# consent, so what the merchant approved lives on the token.
|
|
15
|
+
class OauthApplicationSerializer < V3::BaseSerializer
|
|
16
|
+
typelize name: :string,
|
|
17
|
+
client_id: :string,
|
|
18
|
+
connected: :boolean,
|
|
19
|
+
scopes: [:string, multi: true],
|
|
20
|
+
last_authorized_at: [:string, nullable: true],
|
|
21
|
+
authorized_at: [:string, nullable: true],
|
|
22
|
+
authorized_by: [:string, nullable: true],
|
|
23
|
+
redirect_uri: [:string, nullable: true]
|
|
24
|
+
|
|
25
|
+
attributes :name, :redirect_uri, created_at: :iso8601, updated_at: :iso8601
|
|
26
|
+
|
|
27
|
+
attribute :client_id, &:uid
|
|
28
|
+
|
|
29
|
+
# A registration nobody has connected and one whose access was
|
|
30
|
+
# revoked both carry no scopes, so the list alone cannot tell them
|
|
31
|
+
# apart — and a merchant pressing Revoke has to see that it worked.
|
|
32
|
+
attribute :connected, &:connected?
|
|
33
|
+
|
|
34
|
+
attribute :scopes do |application|
|
|
35
|
+
application.live_token_scopes
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attribute :last_authorized_at do |application|
|
|
39
|
+
application.last_authorized_at&.iso8601
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
attribute :authorized_at do |application|
|
|
43
|
+
application.authorized_at&.iso8601
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Who allowed this, in the words a person is known by. On the screen
|
|
47
|
+
# that decides whether to revoke, the permission alone is half the
|
|
48
|
+
# story.
|
|
49
|
+
attribute :authorized_by do |application|
|
|
50
|
+
application.authorized_by.try(:actor_label)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin API serializer for {Spree::OauthConsentRequest}.
|
|
6
|
+
#
|
|
7
|
+
# Alba directly rather than through {BaseSerializer}: a consent
|
|
8
|
+
# request is a computed view with no id to expose, and inheriting one
|
|
9
|
+
# would put a null `id` in every response.
|
|
10
|
+
class OauthConsentRequestSerializer
|
|
11
|
+
include Alba::Resource
|
|
12
|
+
include Typelizer::DSL
|
|
13
|
+
|
|
14
|
+
typelize client_name: :string,
|
|
15
|
+
scopes: [:string, multi: true],
|
|
16
|
+
grantable_scopes: [:string, multi: true],
|
|
17
|
+
resource: [:string, nullable: true],
|
|
18
|
+
redirect_uri: :string
|
|
19
|
+
|
|
20
|
+
attributes :client_name, :scopes, :grantable_scopes, :resource, :redirect_uri
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Serializer for {Spree::OauthProtectedResource}, the RFC 9728
|
|
6
|
+
# document a client reads to find the authorization server.
|
|
7
|
+
#
|
|
8
|
+
# Alba directly rather than through {BaseSerializer}: the document is
|
|
9
|
+
# a computed view with no id, and inheriting one would put a null
|
|
10
|
+
# `id` in a document the spec defines exactly.
|
|
11
|
+
class OauthProtectedResourceSerializer
|
|
12
|
+
include Alba::Resource
|
|
13
|
+
include Typelizer::DSL
|
|
14
|
+
|
|
15
|
+
typelize resource: :string,
|
|
16
|
+
authorization_servers: [:string, multi: true],
|
|
17
|
+
scopes_supported: [:string, multi: true],
|
|
18
|
+
bearer_methods_supported: [:string, multi: true],
|
|
19
|
+
resource_documentation: :string
|
|
20
|
+
|
|
21
|
+
attributes :resource, :authorization_servers, :scopes_supported,
|
|
22
|
+
:bearer_methods_supported, :resource_documentation
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin API serializer for {Spree::OauthRedirect}.
|
|
6
|
+
class OauthRedirectSerializer
|
|
7
|
+
include Alba::Resource
|
|
8
|
+
include Typelizer::DSL
|
|
9
|
+
|
|
10
|
+
typelize redirect_uri: :string
|
|
11
|
+
|
|
12
|
+
attributes :redirect_uri
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Serializes Spree::PaymentCaptureEvent — one capture of money a
|
|
6
|
+
# payment had authorized. A payment captured in parts has one per part.
|
|
7
|
+
class PaymentCaptureEventSerializer < V3::BaseSerializer
|
|
8
|
+
attributes created_at: :iso8601, updated_at: :iso8601
|
|
9
|
+
|
|
10
|
+
money_attributes :amount
|
|
11
|
+
typelize amount: [:string, nullable: false]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -13,11 +13,18 @@ module Spree
|
|
|
13
13
|
order_id: [:string, nullable: true],
|
|
14
14
|
avs_response: [:string, nullable: true],
|
|
15
15
|
cvv_response_code: [:string, nullable: true],
|
|
16
|
-
cvv_response_message: [:string, nullable: true]
|
|
16
|
+
cvv_response_message: [:string, nullable: true],
|
|
17
|
+
gateway_dashboard_payment_url: [:string, nullable: true],
|
|
18
|
+
avs_risky: [:boolean, comment: 'Whether the card issuer reported that the billing address did not match.'],
|
|
19
|
+
cvv_risky: [:boolean, comment: 'Whether the card issuer reported that the security code did not match.']
|
|
17
20
|
|
|
18
21
|
attributes :metadata, :avs_response, :cvv_response_code, :cvv_response_message,
|
|
22
|
+
:gateway_dashboard_payment_url,
|
|
19
23
|
created_at: :iso8601, updated_at: :iso8601
|
|
20
24
|
|
|
25
|
+
attribute(:avs_risky, &:is_avs_risky?)
|
|
26
|
+
attribute(:cvv_risky, &:is_cvv_risky?)
|
|
27
|
+
|
|
21
28
|
money_attributes :captured_amount
|
|
22
29
|
typelize captured_amount: [:string, nullable: false]
|
|
23
30
|
|
|
@@ -41,6 +48,10 @@ module Spree
|
|
|
41
48
|
serializer.new(payment.source).to_h
|
|
42
49
|
end
|
|
43
50
|
|
|
51
|
+
many :capture_events,
|
|
52
|
+
resource: proc { Spree.api.admin_payment_capture_event_serializer },
|
|
53
|
+
if: proc { expand?('capture_events') }
|
|
54
|
+
|
|
44
55
|
one :order,
|
|
45
56
|
resource: proc { Spree.api.admin_order_serializer },
|
|
46
57
|
if: proc { expand?('order') }
|
|
@@ -5,7 +5,7 @@ module Spree
|
|
|
5
5
|
# Admin-only: certificates are back-office evidence with no storefront
|
|
6
6
|
# surface, so there is no store serializer to extend.
|
|
7
7
|
class TaxExemptionCertificateSerializer < V3::BaseSerializer
|
|
8
|
-
typelize certificate_number: :string,
|
|
8
|
+
typelize certificate_number: :string, tax_exemption_reason_id: :string, status: [:string, enum: Spree::TaxExemptionCertificate.statuses, enum_type_name: 'TaxExemptionCertificateStatus'],
|
|
9
9
|
issuing_authority: [:string, nullable: true],
|
|
10
10
|
issued_at: [:string, nullable: true], expires_at: [:string, nullable: true],
|
|
11
11
|
verified_at: [:string, nullable: true],
|
|
@@ -17,11 +17,15 @@ module Spree
|
|
|
17
17
|
document_url: [:string, nullable: true],
|
|
18
18
|
metadata: 'Record<string, unknown> | null'
|
|
19
19
|
|
|
20
|
-
attributes :certificate_number, :
|
|
20
|
+
attributes :certificate_number, :status, :issuing_authority, :metadata,
|
|
21
21
|
issued_at: :iso8601, expires_at: :iso8601, verified_at: :iso8601,
|
|
22
22
|
created_at: :iso8601, updated_at: :iso8601
|
|
23
23
|
|
|
24
|
-
prefixed_id_attributes :company
|
|
24
|
+
prefixed_id_attributes :company, :tax_exemption_reason
|
|
25
|
+
|
|
26
|
+
one :tax_exemption_reason,
|
|
27
|
+
resource: proc { Spree.api.admin_tax_exemption_reason_serializer },
|
|
28
|
+
if: proc { expand?('tax_exemption_reason') }
|
|
25
29
|
|
|
26
30
|
# The jurisdiction the certificate holds in, in the same vocabulary the
|
|
27
31
|
# tax lines use.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin-only: exemption reasons are recorded on certificates, which are
|
|
6
|
+
# back-office evidence with no storefront surface.
|
|
7
|
+
class TaxExemptionReasonSerializer < V3::BaseSerializer
|
|
8
|
+
typelize name: :string, code: :string, active: :boolean, can_be_deleted: :boolean,
|
|
9
|
+
metadata: 'Record<string, unknown> | null'
|
|
10
|
+
|
|
11
|
+
attributes :name, :code, :active, :metadata, created_at: :iso8601, updated_at: :iso8601
|
|
12
|
+
|
|
13
|
+
# Lets the dashboard hide destructive controls instead of offering a
|
|
14
|
+
# delete that the model will refuse.
|
|
15
|
+
attribute :can_be_deleted, &:can_be_deleted?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -6,7 +6,6 @@ module Spree
|
|
|
6
6
|
# customer-facing. What the customer sees is the label on a tax line.
|
|
7
7
|
class TaxRateSerializer < V3::BaseSerializer
|
|
8
8
|
typelize name: :string,
|
|
9
|
-
included_in_price: :boolean,
|
|
10
9
|
show_rate_in_label: :boolean,
|
|
11
10
|
tax_category_id: [:string, nullable: true],
|
|
12
11
|
store_id: [:string, nullable: true],
|
|
@@ -15,7 +14,7 @@ module Spree
|
|
|
15
14
|
metadata: ['Record<string, unknown>', nullable: true],
|
|
16
15
|
deleted_at: [:string, nullable: true]
|
|
17
16
|
|
|
18
|
-
attributes :name, :
|
|
17
|
+
attributes :name, :show_rate_in_label, :metadata,
|
|
19
18
|
created_at: :iso8601, updated_at: :iso8601, deleted_at: :iso8601
|
|
20
19
|
|
|
21
20
|
rate_attributes :rate, :rate_percent
|
|
@@ -32,8 +32,11 @@ module Spree
|
|
|
32
32
|
# shipped still hold live credentials in the column. Null when the
|
|
33
33
|
# caller cannot read the record the event is about.
|
|
34
34
|
attribute :payload do |delivery|
|
|
35
|
+
# Fails closed: a reader that passes no gate gets nothing. The
|
|
36
|
+
# payload keeps the record body by design, so a caller holding
|
|
37
|
+
# `read_webhooks` alone must not see customer or payment data.
|
|
35
38
|
visible = params[:payload_visible]
|
|
36
|
-
next nil
|
|
39
|
+
next nil unless visible&.call(delivery)
|
|
37
40
|
|
|
38
41
|
Spree::WebhookPayloadRedaction.split(delivery.payload).first
|
|
39
42
|
end
|
|
@@ -224,6 +224,20 @@ module Spree
|
|
|
224
224
|
|
|
225
225
|
private
|
|
226
226
|
|
|
227
|
+
# Adds the fields extensions registered for this class
|
|
228
|
+
# (Spree.api.extension_fields), read from the registry on every
|
|
229
|
+
# render as Spree.ransack is, so nothing is copied onto the class. A
|
|
230
|
+
# field never replaces an attribute the serializer writes itself.
|
|
231
|
+
def attributes_to_hash(object, hash)
|
|
232
|
+
super
|
|
233
|
+
Spree.api.extension_fields.for(self.class).each do |field|
|
|
234
|
+
next if @_attributes.key?(field.name)
|
|
235
|
+
|
|
236
|
+
set_key_and_attribute_body_from(object, field.name, field.reader, hash)
|
|
237
|
+
end
|
|
238
|
+
hash
|
|
239
|
+
end
|
|
240
|
+
|
|
227
241
|
# Override Alba's fetch_attribute to automatically inject nested expand params
|
|
228
242
|
# into child serializers via nested_params.
|
|
229
243
|
# Uses `key` (the public API name, e.g. :categories) for expand lookup,
|