spree_api 5.4.0.beta6 → 5.4.0.beta8
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/README.md +3 -3
- data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +45 -0
- data/app/controllers/concerns/spree/api/v3/error_handler.rb +22 -15
- data/app/controllers/concerns/spree/api/v3/order_lock.rb +4 -4
- data/app/controllers/spree/api/v3/store/carts/coupon_codes_controller.rb +57 -0
- data/app/controllers/spree/api/v3/store/{orders/line_items_controller.rb → carts/items_controller.rb} +18 -31
- data/app/controllers/spree/api/v3/store/carts/payment_methods_controller.rb +24 -0
- data/app/controllers/spree/api/v3/store/{orders → carts}/payment_sessions_controller.rb +14 -19
- data/app/controllers/spree/api/v3/store/{orders → carts}/payments_controller.rb +18 -29
- data/app/controllers/spree/api/v3/store/carts/shipments_controller.rb +65 -0
- data/app/controllers/spree/api/v3/store/{orders → carts}/store_credits_controller.rb +9 -10
- data/app/controllers/spree/api/v3/store/carts_controller.rb +176 -0
- data/app/controllers/spree/api/v3/store/customer/orders_controller.rb +1 -1
- data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +89 -0
- data/app/controllers/spree/api/v3/store/orders_controller.rb +21 -114
- data/app/controllers/spree/api/v3/store/products_controller.rb +1 -1
- data/app/controllers/spree/api/v3/webhooks/payments_controller.rb +52 -0
- data/app/serializers/spree/api/v3/admin/allowed_origin_serializer.rb +21 -0
- data/app/serializers/spree/api/v3/admin/media_serializer.rb +17 -0
- data/app/serializers/spree/api/v3/admin/order_serializer.rb +2 -2
- data/app/serializers/spree/api/v3/admin/product_serializer.rb +8 -4
- data/app/serializers/spree/api/v3/admin/variant_serializer.rb +8 -3
- data/app/serializers/spree/api/v3/base_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/cart_promotion_serializer.rb +18 -0
- data/app/serializers/spree/api/v3/cart_serializer.rb +56 -0
- data/app/serializers/spree/api/v3/media_serializer.rb +60 -0
- data/app/serializers/spree/api/v3/order_serializer.rb +7 -9
- data/app/serializers/spree/api/v3/product_serializer.rb +10 -6
- data/app/serializers/spree/api/v3/variant_serializer.rb +14 -9
- data/config/locales/en.yml +3 -0
- data/config/routes.rb +23 -24
- data/lib/spree/api/configuration.rb +1 -0
- data/lib/spree/api/dependencies.rb +4 -2
- data/lib/spree/api/openapi/schema_helper.rb +28 -0
- metadata +22 -17
- data/app/controllers/concerns/spree/api/v3/order_concern.rb +0 -46
- data/app/controllers/spree/api/v3/store/cart_controller.rb +0 -97
- data/app/controllers/spree/api/v3/store/orders/coupon_codes_controller.rb +0 -73
- data/app/controllers/spree/api/v3/store/orders/payment_methods_controller.rb +0 -43
- data/app/controllers/spree/api/v3/store/orders/shipments_controller.rb +0 -56
- data/app/serializers/spree/api/v3/admin/image_serializer.rb +0 -10
- data/app/serializers/spree/api/v3/image_serializer.rb +0 -43
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Store
|
|
5
|
+
class CartsController < Store::ResourceController
|
|
6
|
+
include Spree::Api::V3::CartResolvable
|
|
7
|
+
include Spree::Api::V3::OrderLock
|
|
8
|
+
|
|
9
|
+
skip_before_action :set_resource
|
|
10
|
+
prepend_before_action :require_authentication!, only: [:index, :associate]
|
|
11
|
+
|
|
12
|
+
# GET /api/v3/store/carts/:id
|
|
13
|
+
# Returns cart by prefixed ID
|
|
14
|
+
# Auto-advances the checkout state machine so that shipments and
|
|
15
|
+
# payment requirements are up-to-date (temporary until Spree 6 removes
|
|
16
|
+
# the state machine).
|
|
17
|
+
def show
|
|
18
|
+
@cart = find_cart
|
|
19
|
+
|
|
20
|
+
if @cart.ship_address_id.present? && @cart.shipments.empty?
|
|
21
|
+
ActiveRecord::Base.connected_to(role: :writing) do
|
|
22
|
+
with_order_lock { Spree::Checkout::Advance.call(order: @cart) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
render_cart
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# POST /api/v3/store/carts
|
|
30
|
+
# Creates a new shopping cart (order)
|
|
31
|
+
# Can be created by guests or authenticated customers
|
|
32
|
+
def create
|
|
33
|
+
result = Spree::Carts::Create.call(
|
|
34
|
+
params: permitted_params.merge(
|
|
35
|
+
user: current_user,
|
|
36
|
+
store: current_store,
|
|
37
|
+
currency: current_currency,
|
|
38
|
+
locale: current_locale
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if result.success?
|
|
43
|
+
@cart = result.value
|
|
44
|
+
render_cart(status: :created)
|
|
45
|
+
else
|
|
46
|
+
render_service_error(result.error.to_s)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# PATCH /api/v3/store/carts/:id
|
|
51
|
+
# Updates cart info (email, addresses, special instructions).
|
|
52
|
+
# Auto-advances to the next checkout step when possible.
|
|
53
|
+
def update
|
|
54
|
+
find_cart!
|
|
55
|
+
|
|
56
|
+
with_order_lock do
|
|
57
|
+
result = Spree::Carts::Update.call(
|
|
58
|
+
cart: @cart,
|
|
59
|
+
params: permitted_params
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if result.success?
|
|
63
|
+
render_cart
|
|
64
|
+
else
|
|
65
|
+
render_service_error(result.error, code: ERROR_CODES[:validation_error])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# DELETE /api/v3/store/carts/:id
|
|
71
|
+
# Deletes/abandons the cart
|
|
72
|
+
def destroy
|
|
73
|
+
find_cart!
|
|
74
|
+
|
|
75
|
+
result = Spree.cart_destroy_service.call(order: @cart)
|
|
76
|
+
|
|
77
|
+
if result.success?
|
|
78
|
+
head :no_content
|
|
79
|
+
else
|
|
80
|
+
render_service_error(result.error.to_s)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# PATCH /api/v3/store/carts/:id/associate
|
|
85
|
+
# Associates a guest cart with the currently authenticated user
|
|
86
|
+
# Requires: JWT authentication + cart ID in URL
|
|
87
|
+
def associate
|
|
88
|
+
@cart = find_cart_for_association
|
|
89
|
+
|
|
90
|
+
result = Spree.cart_associate_service.call(guest_order: @cart, user: current_user, guest_only: true)
|
|
91
|
+
|
|
92
|
+
if result.success?
|
|
93
|
+
render_cart
|
|
94
|
+
else
|
|
95
|
+
render_service_error(result.error.to_s)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# POST /api/v3/store/carts/:id/complete
|
|
100
|
+
# Completes the checkout — returns Order (not Cart).
|
|
101
|
+
# Idempotent: if the cart is already completed, falls back to the
|
|
102
|
+
# orders scope and returns the completed order.
|
|
103
|
+
def complete
|
|
104
|
+
find_cart!
|
|
105
|
+
|
|
106
|
+
result = Spree::Dependencies.carts_complete_service.constantize.call(cart: @cart)
|
|
107
|
+
|
|
108
|
+
if result.success?
|
|
109
|
+
@cart = result.value
|
|
110
|
+
render_order
|
|
111
|
+
else
|
|
112
|
+
render_service_error(
|
|
113
|
+
result.error.to_s.presence || 'Could not complete checkout',
|
|
114
|
+
code: ERROR_CODES[:cart_cannot_complete]
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
rescue ActiveRecord::RecordNotFound
|
|
118
|
+
@cart = current_store.orders.complete.find_by_prefix_id!(params[:id])
|
|
119
|
+
authorize!(:show, @cart, cart_token)
|
|
120
|
+
|
|
121
|
+
render_order
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
protected
|
|
125
|
+
|
|
126
|
+
def model_class
|
|
127
|
+
Spree::Order
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def serializer_class
|
|
131
|
+
Spree.api.cart_serializer
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def scope
|
|
135
|
+
current_store.carts.where(user: current_user).order(updated_at: :desc)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
def permitted_params
|
|
141
|
+
params.permit(
|
|
142
|
+
:email,
|
|
143
|
+
:special_instructions,
|
|
144
|
+
:currency,
|
|
145
|
+
:locale,
|
|
146
|
+
:ship_address_id,
|
|
147
|
+
:bill_address_id,
|
|
148
|
+
ship_address: address_params,
|
|
149
|
+
bill_address: address_params,
|
|
150
|
+
metadata: {},
|
|
151
|
+
items: item_params
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def address_params
|
|
156
|
+
[
|
|
157
|
+
:id, :firstname, :lastname, :address1, :address2,
|
|
158
|
+
:city, :zipcode, :phone, :company,
|
|
159
|
+
:country_iso, :state_abbr, :state_name, :quick_checkout
|
|
160
|
+
]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def item_params
|
|
164
|
+
[:variant_id, :quantity, { metadata: {}, options: {} }]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Find incomplete cart for associate action.
|
|
168
|
+
# Only finds guest carts (no user) or carts already owned by current user (idempotent).
|
|
169
|
+
def find_cart_for_association
|
|
170
|
+
current_store.carts.where(user: [nil, current_user]).find_by_prefix_id!(params[:id])
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Api
|
|
5
|
+
module V3
|
|
6
|
+
module Store
|
|
7
|
+
module Customer
|
|
8
|
+
class PasswordResetsController < Store::BaseController
|
|
9
|
+
rate_limit to: Spree::Api::Config[:rate_limit_password_reset],
|
|
10
|
+
within: Spree::Api::Config[:rate_limit_window].seconds,
|
|
11
|
+
store: Rails.cache,
|
|
12
|
+
with: RATE_LIMIT_RESPONSE
|
|
13
|
+
|
|
14
|
+
skip_before_action :authenticate_user
|
|
15
|
+
|
|
16
|
+
# POST /api/v3/store/customer/password_resets
|
|
17
|
+
def create
|
|
18
|
+
redirect_url = params[:redirect_url]
|
|
19
|
+
|
|
20
|
+
# Validate redirect_url against allowed origins (secure by default).
|
|
21
|
+
# If no allowed origins are configured, redirect_url is silently ignored
|
|
22
|
+
# to prevent open redirect / token exfiltration attacks.
|
|
23
|
+
if redirect_url.present?
|
|
24
|
+
if current_store.allowed_origins.exists? && current_store.allowed_origin?(redirect_url)
|
|
25
|
+
# redirect_url is valid — keep it
|
|
26
|
+
else
|
|
27
|
+
redirect_url = nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
user = Spree.user_class.find_by(email: params[:email])
|
|
32
|
+
|
|
33
|
+
if user
|
|
34
|
+
token = user.generate_token_for(:password_reset)
|
|
35
|
+
event_payload = { reset_token: token, email: user.email }
|
|
36
|
+
event_payload[:redirect_url] = redirect_url if redirect_url.present?
|
|
37
|
+
user.publish_event('customer.password_reset_requested', event_payload)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Always return 202 to prevent email enumeration
|
|
41
|
+
render json: { message: Spree.t(:password_reset_requested, scope: :api) }, status: :accepted
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# PATCH /api/v3/store/customer/password_resets/:id
|
|
45
|
+
def update
|
|
46
|
+
user = Spree.user_class.find_by_password_reset_token(params[:id])
|
|
47
|
+
|
|
48
|
+
unless user
|
|
49
|
+
return render_error(
|
|
50
|
+
code: ERROR_CODES[:password_reset_token_invalid],
|
|
51
|
+
message: Spree.t(:password_reset_token_invalid, scope: :api),
|
|
52
|
+
status: :unprocessable_content
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if user.update(password: params[:password], password_confirmation: params[:password_confirmation])
|
|
57
|
+
jwt = generate_jwt(user)
|
|
58
|
+
user.publish_event('customer.password_reset')
|
|
59
|
+
|
|
60
|
+
render json: {
|
|
61
|
+
token: jwt,
|
|
62
|
+
user: serializer_class.new(user, params: serializer_params).to_h
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
render_errors(user.errors)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
protected
|
|
70
|
+
|
|
71
|
+
def serializer_class
|
|
72
|
+
Spree.api.customer_serializer
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def serializer_params
|
|
76
|
+
{
|
|
77
|
+
store: current_store,
|
|
78
|
+
locale: current_locale,
|
|
79
|
+
currency: current_currency,
|
|
80
|
+
user: nil,
|
|
81
|
+
includes: []
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -2,133 +2,40 @@ module Spree
|
|
|
2
2
|
module Api
|
|
3
3
|
module V3
|
|
4
4
|
module Store
|
|
5
|
-
class OrdersController <
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
class OrdersController < Store::BaseController
|
|
6
|
+
# GET /api/v3/store/orders/:id
|
|
7
|
+
# Single order lookup — accessible via order token (guests) or JWT (authenticated users)
|
|
8
|
+
before_action :find_order!
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
before_action :set_resource, only: [:show, :update, :next, :advance, :complete]
|
|
12
|
-
|
|
13
|
-
# PATCH /api/v3/store/orders/:id
|
|
14
|
-
#
|
|
15
|
-
# Accepts flat parameters:
|
|
16
|
-
# {
|
|
17
|
-
# "email": "customer@example.com",
|
|
18
|
-
# "currency": "EUR",
|
|
19
|
-
# "ship_address": { "firstname": "John", "country_iso": "US", ... },
|
|
20
|
-
# "bill_address": { "firstname": "John", "country_iso": "US", ... }
|
|
21
|
-
# }
|
|
22
|
-
#
|
|
23
|
-
def update
|
|
24
|
-
with_order_lock do
|
|
25
|
-
result = Spree.order_update_service.call(
|
|
26
|
-
order: @order,
|
|
27
|
-
params: order_params
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
if result.success?
|
|
31
|
-
render json: serialize_resource(@order.reload)
|
|
32
|
-
else
|
|
33
|
-
render_service_error(result.error, code: ERROR_CODES[:validation_error])
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# PATCH /api/v3/store/orders/:id/next
|
|
39
|
-
def next
|
|
40
|
-
with_order_lock do
|
|
41
|
-
result = Spree.checkout_next_service.call(order: @order)
|
|
42
|
-
|
|
43
|
-
if result.success?
|
|
44
|
-
render json: serialize_resource(@order)
|
|
45
|
-
else
|
|
46
|
-
render_service_error(result.error, code: ERROR_CODES[:order_cannot_transition])
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# PATCH /api/v3/store/orders/:id/advance
|
|
52
|
-
def advance
|
|
53
|
-
with_order_lock do
|
|
54
|
-
result = Spree.checkout_advance_service.call(order: @order)
|
|
55
|
-
|
|
56
|
-
if result.success?
|
|
57
|
-
render json: serialize_resource(@order)
|
|
58
|
-
else
|
|
59
|
-
render_service_error(result.error, code: ERROR_CODES[:order_cannot_transition])
|
|
60
|
-
end
|
|
61
|
-
end
|
|
10
|
+
def show
|
|
11
|
+
render json: serializer_class.new(@order, params: serializer_params).to_h
|
|
62
12
|
end
|
|
63
13
|
|
|
64
|
-
|
|
65
|
-
def complete
|
|
66
|
-
with_order_lock do
|
|
67
|
-
result = Spree.checkout_complete_service.call(order: @order)
|
|
14
|
+
private
|
|
68
15
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
render_service_error(result.error, code: ERROR_CODES[:order_already_completed])
|
|
73
|
-
end
|
|
74
|
-
end
|
|
16
|
+
def find_order!
|
|
17
|
+
@order = scope.find_by_prefix_id!(params[:id])
|
|
18
|
+
authorize!(:show, @order, order_token)
|
|
75
19
|
end
|
|
76
20
|
|
|
77
|
-
protected
|
|
78
|
-
|
|
79
|
-
# Override scope to avoid accessible_by (Order permissions use blocks)
|
|
80
21
|
def scope
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# override authorize_resource! to pass the order token
|
|
92
|
-
# Maps custom checkout actions to appropriate permissions
|
|
93
|
-
def authorize_resource!(resource = @resource, action = action_name.to_sym)
|
|
94
|
-
mapped_action = case action
|
|
95
|
-
when :next, :advance, :complete
|
|
96
|
-
:update # Checkout actions require update (non-completed order)
|
|
97
|
-
else
|
|
98
|
-
action
|
|
99
|
-
end
|
|
100
|
-
authorize!(mapped_action, resource, order_token)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def model_class
|
|
104
|
-
Spree::Order
|
|
22
|
+
base = current_store.orders.complete
|
|
23
|
+
|
|
24
|
+
if current_user.present?
|
|
25
|
+
base.where(user: current_user)
|
|
26
|
+
elsif order_token.present?
|
|
27
|
+
base.where(token: order_token)
|
|
28
|
+
else
|
|
29
|
+
base.none
|
|
30
|
+
end
|
|
105
31
|
end
|
|
106
32
|
|
|
107
33
|
def serializer_class
|
|
108
34
|
Spree.api.order_serializer
|
|
109
35
|
end
|
|
110
36
|
|
|
111
|
-
def
|
|
112
|
-
|
|
113
|
-
:email,
|
|
114
|
-
:currency,
|
|
115
|
-
:locale,
|
|
116
|
-
:special_instructions,
|
|
117
|
-
:ship_address_id,
|
|
118
|
-
:bill_address_id,
|
|
119
|
-
ship_address: address_params,
|
|
120
|
-
bill_address: address_params,
|
|
121
|
-
metadata: {},
|
|
122
|
-
line_items: [:variant_id, :quantity, { metadata: {}, options: {} }]
|
|
123
|
-
)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def address_params
|
|
127
|
-
[
|
|
128
|
-
:id, :firstname, :lastname, :address1, :address2,
|
|
129
|
-
:city, :zipcode, :phone, :company,
|
|
130
|
-
:country_iso, :state_abbr, :state_name, :quick_checkout
|
|
131
|
-
]
|
|
37
|
+
def order_token
|
|
38
|
+
request.headers['x-spree-token']
|
|
132
39
|
end
|
|
133
40
|
end
|
|
134
41
|
end
|
|
@@ -41,7 +41,7 @@ module Spree
|
|
|
41
41
|
# these scopes are not automatically picked by ar_lazy_preload gem and we need to explicitly include them
|
|
42
42
|
def scope_includes
|
|
43
43
|
[
|
|
44
|
-
|
|
44
|
+
primary_media: [attachment_attachment: :blob],
|
|
45
45
|
master: [:prices, stock_items: :stock_location],
|
|
46
46
|
variants: [:prices, stock_items: :stock_location]
|
|
47
47
|
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Webhooks
|
|
5
|
+
class PaymentsController < ActionController::API
|
|
6
|
+
include ActionController::RateLimiting
|
|
7
|
+
|
|
8
|
+
RATE_LIMIT_RESPONSE = -> {
|
|
9
|
+
[429, { 'Content-Type' => 'application/json', 'Retry-After' => '60' },
|
|
10
|
+
[{ error: { code: 'rate_limit_exceeded', message: 'Too many requests' } }.to_json]]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
rate_limit to: 120, within: 1.minute,
|
|
14
|
+
store: Rails.cache,
|
|
15
|
+
by: -> { request.remote_ip },
|
|
16
|
+
with: RATE_LIMIT_RESPONSE
|
|
17
|
+
|
|
18
|
+
# POST /api/v3/webhooks/payments/:payment_method_id
|
|
19
|
+
#
|
|
20
|
+
# Verifies the webhook signature synchronously (returns 401 if invalid),
|
|
21
|
+
# then enqueues async processing and returns 200 immediately.
|
|
22
|
+
def create
|
|
23
|
+
payment_method = Spree::PaymentMethod.find_by_prefix_id!(params[:payment_method_id])
|
|
24
|
+
|
|
25
|
+
# Signature verification must be synchronous — invalid = 401
|
|
26
|
+
result = payment_method.parse_webhook_event(request.raw_post, request.headers)
|
|
27
|
+
|
|
28
|
+
# Unsupported event — acknowledge receipt
|
|
29
|
+
return head :ok if result.nil?
|
|
30
|
+
|
|
31
|
+
# Process asynchronously — gateways have timeout limits and will
|
|
32
|
+
# retry on timeouts, so we must return 200 quickly.
|
|
33
|
+
Spree::Payments::HandleWebhookJob.perform_later(
|
|
34
|
+
payment_method_id: payment_method.id,
|
|
35
|
+
action: result[:action].to_s,
|
|
36
|
+
payment_session_id: result[:payment_session].id
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
head :ok
|
|
40
|
+
rescue Spree::PaymentMethod::WebhookSignatureError
|
|
41
|
+
head :unauthorized
|
|
42
|
+
rescue ActiveRecord::RecordNotFound
|
|
43
|
+
head :not_found
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
Rails.error.report(e, source: 'spree.webhooks.payments')
|
|
46
|
+
head :ok
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Api
|
|
5
|
+
module V3
|
|
6
|
+
module Admin
|
|
7
|
+
class AllowedOriginSerializer < V3::BaseSerializer
|
|
8
|
+
typelize store_id: :string
|
|
9
|
+
|
|
10
|
+
attributes :id, :origin
|
|
11
|
+
|
|
12
|
+
attribute :store_id do |allowed_origin|
|
|
13
|
+
allowed_origin.store&.prefixed_id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attributes created_at: :iso8601, updated_at: :iso8601
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class MediaSerializer < V3::MediaSerializer
|
|
6
|
+
typelize viewable_type: :string, viewable_id: :string
|
|
7
|
+
|
|
8
|
+
attribute :viewable_id do |asset|
|
|
9
|
+
asset.viewable&.prefixed_id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attributes :viewable_type
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -43,8 +43,8 @@ module Spree
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
# Override inherited associations to use admin serializers
|
|
46
|
-
many :order_promotions, resource: Spree.api.admin_order_promotion_serializer, if: proc { expand?('
|
|
47
|
-
many :line_items, resource: Spree.api.admin_line_item_serializer, if: proc { expand?('
|
|
46
|
+
many :order_promotions, key: :promotions, resource: Spree.api.admin_order_promotion_serializer, if: proc { expand?('promotions') }
|
|
47
|
+
many :line_items, key: :items, resource: Spree.api.admin_line_item_serializer, if: proc { expand?('items') }
|
|
48
48
|
many :shipments, resource: Spree.api.admin_shipment_serializer, if: proc { expand?('shipments') }
|
|
49
49
|
many :payments, resource: Spree.api.admin_payment_serializer, if: proc { expand?('payments') }
|
|
50
50
|
|
|
@@ -37,10 +37,14 @@ module Spree
|
|
|
37
37
|
resource: Spree.api.admin_variant_serializer,
|
|
38
38
|
if: proc { expand?('master_variant') }
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
one :primary_media,
|
|
41
|
+
resource: Spree.api.admin_media_serializer,
|
|
42
|
+
if: proc { expand?('primary_media') }
|
|
43
|
+
|
|
44
|
+
many :gallery_media,
|
|
45
|
+
key: :media,
|
|
46
|
+
resource: Spree.api.admin_media_serializer,
|
|
47
|
+
if: proc { expand?('media') }
|
|
44
48
|
|
|
45
49
|
many :option_types,
|
|
46
50
|
resource: Spree.api.admin_option_type_serializer,
|
|
@@ -20,9 +20,14 @@ module Spree
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# Override inherited associations to use admin serializers
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
one :primary_media,
|
|
24
|
+
resource: Spree.api.admin_media_serializer,
|
|
25
|
+
if: proc { expand?('primary_media') }
|
|
26
|
+
|
|
27
|
+
many :gallery_media,
|
|
28
|
+
key: :media,
|
|
29
|
+
resource: Spree.api.admin_media_serializer,
|
|
30
|
+
if: proc { expand?('media') }
|
|
26
31
|
|
|
27
32
|
many :option_values, resource: Spree.api.admin_option_value_serializer
|
|
28
33
|
|
|
@@ -38,7 +38,7 @@ module Spree
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
# Check if an association should be expanded
|
|
41
|
-
# Supports dot notation: expand?('variants') matches both 'variants' and 'variants.
|
|
41
|
+
# Supports dot notation: expand?('variants') matches both 'variants' and 'variants.media'
|
|
42
42
|
def expand?(name)
|
|
43
43
|
name = name.to_s
|
|
44
44
|
expands.any? { |e| e == name || e.start_with?("#{name}.") }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
# Cart-facing promotion serializer.
|
|
5
|
+
# Same data as OrderPromotionSerializer but IDs use the cp_ prefix.
|
|
6
|
+
class CartPromotionSerializer < BaseSerializer
|
|
7
|
+
typelize name: :string, description: [:string, nullable: true], code: [:string, nullable: true],
|
|
8
|
+
amount: :string, display_amount: :string, promotion_id: :string
|
|
9
|
+
|
|
10
|
+
attribute :promotion_id do |cart_promotion|
|
|
11
|
+
cart_promotion.promotion&.prefixed_id
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attributes :name, :description, :code, :amount, :display_amount
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
# Store API Cart Serializer
|
|
5
|
+
# Pre-purchase cart data with checkout progression info
|
|
6
|
+
class CartSerializer < BaseSerializer
|
|
7
|
+
typelize number: :string, current_step: :string, completed_steps: 'string[]', token: :string, email: [:string, nullable: true],
|
|
8
|
+
special_instructions: [:string, nullable: true], currency: :string, locale: [:string, nullable: true], item_count: :number,
|
|
9
|
+
requirements: 'Array<{step: string, field: string, message: string}>',
|
|
10
|
+
item_total: :string, display_item_total: :string,
|
|
11
|
+
ship_total: :string, display_ship_total: :string,
|
|
12
|
+
adjustment_total: :string, display_adjustment_total: :string,
|
|
13
|
+
promo_total: :string, display_promo_total: :string,
|
|
14
|
+
tax_total: :string, display_tax_total: :string,
|
|
15
|
+
included_tax_total: :string, display_included_tax_total: :string,
|
|
16
|
+
additional_tax_total: :string, display_additional_tax_total: :string,
|
|
17
|
+
total: :string, display_total: :string,
|
|
18
|
+
bill_address: { nullable: true }, ship_address: { nullable: true }
|
|
19
|
+
|
|
20
|
+
# Override ID to use cart_ prefix
|
|
21
|
+
attribute :id do |order|
|
|
22
|
+
"cart_#{Spree::PrefixedId::SQIDS.encode([order.id])}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attributes :number, :token, :email, :special_instructions,
|
|
26
|
+
:currency, :locale, :item_count,
|
|
27
|
+
:item_total, :display_item_total, :ship_total, :display_ship_total,
|
|
28
|
+
:adjustment_total, :display_adjustment_total, :promo_total, :display_promo_total,
|
|
29
|
+
:tax_total, :display_tax_total, :included_tax_total, :display_included_tax_total,
|
|
30
|
+
:additional_tax_total, :display_additional_tax_total, :total, :display_total,
|
|
31
|
+
created_at: :iso8601, updated_at: :iso8601
|
|
32
|
+
|
|
33
|
+
attribute :current_step do |order|
|
|
34
|
+
order.current_checkout_step
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
attribute :completed_steps do |order|
|
|
38
|
+
order.completed_checkout_steps
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
attribute :requirements do |order|
|
|
42
|
+
Spree::Checkout::Requirements.new(order).call
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
many :cart_promotions, key: :promotions, resource: Spree.api.cart_promotion_serializer
|
|
46
|
+
many :line_items, key: :items, resource: Spree.api.line_item_serializer
|
|
47
|
+
many :shipments, resource: Spree.api.shipment_serializer
|
|
48
|
+
many :payments, resource: Spree.api.payment_serializer
|
|
49
|
+
one :bill_address, resource: Spree.api.address_serializer
|
|
50
|
+
one :ship_address, resource: Spree.api.address_serializer
|
|
51
|
+
|
|
52
|
+
many :payment_methods, resource: Spree.api.payment_method_serializer
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|