solidus_frontend 2.7.4 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of solidus_frontend might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +7 -5
- data/app/assets/images/favicon.ico +0 -0
- data/app/assets/javascripts/spree/frontend/checkout/coupon-code.js +1 -1
- data/app/assets/stylesheets/spree/frontend/screen.css.scss +3 -0
- data/app/controllers/spree/checkout_controller.rb +19 -0
- data/app/controllers/spree/coupon_codes_controller.rb +35 -0
- data/app/controllers/spree/locale_controller.rb +1 -1
- data/app/controllers/spree/orders_controller.rb +1 -0
- data/app/views/spree/checkout/_coupon_code.html.erb +1 -1
- data/app/views/spree/checkout/_delivery.html.erb +6 -2
- data/app/views/spree/coupon_codes/new.html.erb +6 -0
- data/app/views/spree/orders/_form.html.erb +1 -1
- data/app/views/spree/orders/_line_item.html.erb +3 -5
- data/app/views/spree/orders/edit.html.erb +5 -6
- data/app/views/spree/products/_image.html.erb +4 -1
- data/app/views/spree/products/_thumbnails.html.erb +10 -8
- data/app/views/spree/shared/_image.html.erb +4 -4
- data/app/views/spree/shared/_locale_selector.html.erb +1 -1
- data/app/views/spree/shared/_order_details.html.erb +3 -5
- data/app/views/spree/shared/_products.html.erb +1 -1
- data/config/routes.rb +1 -0
- data/lib/spree/frontend.rb +1 -1
- data/solidus_frontend.gemspec +1 -1
- data/spec/controllers/spree/checkout_controller_spec.rb +50 -6
- data/spec/controllers/spree/orders_controller_ability_spec.rb +0 -3
- data/spec/controllers/spree/orders_controller_spec.rb +4 -0
- data/spec/controllers/spree/products_controller_spec.rb +3 -3
- data/spec/controllers/spree/taxons_controller_spec.rb +1 -1
- data/spec/features/checkout_confirm_insufficient_stock_spec.rb +71 -0
- data/spec/features/checkout_spec.rb +1 -1
- data/spec/features/coupon_code_spec.rb +14 -14
- data/spec/features/promotion_code_invalidation_spec.rb +2 -2
- data/spec/features/quantity_promotions_spec.rb +9 -9
- data/spec/spec_helper.rb +1 -0
- data/spec/support/features/fill_in_with_force.rb +12 -0
- metadata +13 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b78e6782c7aaf5de2799b93c100eb97e8431a5e9b865fcc202886e8c28cc008
|
4
|
+
data.tar.gz: 1e17148b86ae7d09454ecb6f8fe5b4589324d75b35cdc5aa410a33318df27db4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5267cc2ab0c37977c64c5aad4be7643a7bb45fc6368826632c012b3945fd3f1dfe4f47586c8a805c6e229b0317c505f663a44d685b788efae1df9f9784e768bf
|
7
|
+
data.tar.gz: 61d8f2a41bdacb457fd500965e9308996b5f005d049a2ee5a4b012d437b649510509d54ec87be3bbdb7f829e88d03ba5de6001d0614357bef01311c6c5cb1634
|
data/README.md
CHANGED
@@ -11,19 +11,19 @@ Solidus provides a generator to help with copying the right view into your host
|
|
11
11
|
|
12
12
|
Simply call the generator to copy all views into your host app.
|
13
13
|
|
14
|
-
```
|
14
|
+
```bash
|
15
15
|
$ bundle exec rails g solidus:views:override
|
16
16
|
```
|
17
17
|
|
18
18
|
If you only want to copy certain views into your host app, you can provide the `--only` argument:
|
19
19
|
|
20
|
-
```
|
20
|
+
```bash
|
21
21
|
$ bundle exec rails g solidus:views:override --only products/show
|
22
22
|
```
|
23
23
|
|
24
24
|
The argument to `--only` can also be a substring of the name of the view from the `app/views/spree` folder:
|
25
25
|
|
26
|
-
```
|
26
|
+
```bash
|
27
27
|
$ bundle exec rails g solidus:views:override --only product
|
28
28
|
```
|
29
29
|
|
@@ -31,10 +31,12 @@ This will copy all views whose directory or filename contains the string "produc
|
|
31
31
|
|
32
32
|
### Handle upgrades
|
33
33
|
|
34
|
-
After upgrading
|
34
|
+
After upgrading Solidus to a new version run the generator again and follow on screen instructions.
|
35
35
|
|
36
36
|
## Testing
|
37
37
|
|
38
38
|
Run the tests
|
39
39
|
|
40
|
-
|
40
|
+
```bash
|
41
|
+
bundle exec rspec
|
42
|
+
```
|
Binary file
|
@@ -14,7 +14,7 @@ Spree.onCouponCodeApply = function(e) {
|
|
14
14
|
coupon_code: couponCode
|
15
15
|
};
|
16
16
|
var req = Spree.ajax({
|
17
|
-
method:
|
17
|
+
method: 'POST',
|
18
18
|
url: Spree.routes.apply_coupon_code(Spree.current_order_id),
|
19
19
|
data: JSON.stringify(data),
|
20
20
|
contentType: "application/json"
|
@@ -24,6 +24,7 @@ module Spree
|
|
24
24
|
helper 'spree/orders'
|
25
25
|
|
26
26
|
rescue_from Spree::Core::GatewayError, with: :rescue_from_spree_gateway_error
|
27
|
+
rescue_from Spree::Order::InsufficientStock, with: :insufficient_stock_error
|
27
28
|
|
28
29
|
# Updates the order and advances to the next state (when possible.)
|
29
30
|
def update
|
@@ -168,6 +169,7 @@ module Spree
|
|
168
169
|
|
169
170
|
def apply_coupon_code
|
170
171
|
if update_params[:coupon_code].present?
|
172
|
+
Spree::Deprecation.warn('This endpoint is deprecated. Please use `Spree::CouponCodesController#create` endpoint instead.')
|
171
173
|
@order.coupon_code = update_params[:coupon_code]
|
172
174
|
|
173
175
|
handler = PromotionHandler::Coupon.new(@order).apply
|
@@ -232,5 +234,22 @@ module Spree
|
|
232
234
|
def check_authorization
|
233
235
|
authorize!(:edit, current_order, cookies.signed[:guest_token])
|
234
236
|
end
|
237
|
+
|
238
|
+
def insufficient_stock_error
|
239
|
+
packages = @order.shipments.map(&:to_package)
|
240
|
+
if packages.empty?
|
241
|
+
flash[:error] = I18n.t('spree.insufficient_stock_for_order')
|
242
|
+
redirect_to cart_path
|
243
|
+
else
|
244
|
+
availability_validator = Spree::Stock::AvailabilityValidator.new
|
245
|
+
unavailable_items = @order.line_items.reject { |line_item| availability_validator.validate(line_item) }
|
246
|
+
if unavailable_items.any?
|
247
|
+
item_names = unavailable_items.map(&:name).to_sentence
|
248
|
+
flash[:error] = t('spree.inventory_error_flash_for_insufficient_shipment_quantity', unavailable_items: item_names)
|
249
|
+
@order.restart_checkout_flow
|
250
|
+
redirect_to spree.checkout_state_path(@order.state)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
235
254
|
end
|
236
255
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class CouponCodesController < Spree::StoreController
|
5
|
+
before_action :load_order, only: :create
|
6
|
+
around_action :lock_order, only: :create
|
7
|
+
|
8
|
+
def create
|
9
|
+
authorize! :update, @order, cookies.signed[:guest_token]
|
10
|
+
|
11
|
+
if params[:coupon_code].present?
|
12
|
+
@order.coupon_code = params[:coupon_code]
|
13
|
+
handler = PromotionHandler::Coupon.new(@order).apply
|
14
|
+
|
15
|
+
respond_with(@order) do |format|
|
16
|
+
format.html do
|
17
|
+
if handler.successful?
|
18
|
+
flash[:success] = handler.success
|
19
|
+
redirect_to cart_path
|
20
|
+
else
|
21
|
+
flash.now[:error] = handler.error
|
22
|
+
render 'spree/coupon_codes/new'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def load_order
|
32
|
+
@order = current_order
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -7,7 +7,7 @@ module Spree
|
|
7
7
|
requested_locale = params[:switch_to_locale] || params[:locale]
|
8
8
|
|
9
9
|
if requested_locale && available_locales.map(&:to_s).include?(requested_locale)
|
10
|
-
session[
|
10
|
+
session[set_user_language_locale_key] = requested_locale
|
11
11
|
I18n.locale = requested_locale
|
12
12
|
flash.notice = t('spree.locale_changed')
|
13
13
|
else
|
@@ -122,6 +122,7 @@ module Spree
|
|
122
122
|
|
123
123
|
def apply_coupon_code
|
124
124
|
if order_params[:coupon_code].present?
|
125
|
+
Spree::Deprecation.warn('This endpoint is deprecated. Please use `Spree::CouponCodesController#create` endpoint instead.')
|
125
126
|
@order.coupon_code = order_params[:coupon_code]
|
126
127
|
|
127
128
|
handler = PromotionHandler::Coupon.new(@order).apply
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<div class="coupon-code" data-hook='coupon_code'>
|
2
2
|
<%= form_for order, url: update_checkout_path(order.state) do |form| %>
|
3
3
|
<%= form.label :coupon_code %>
|
4
|
-
<%= form.text_field :coupon_code, placeholder:
|
4
|
+
<%= form.text_field :coupon_code, placeholder: true %>
|
5
5
|
|
6
6
|
<button type="submit" class="button coupon-code-apply-button" id="coupon-code-apply-button">
|
7
7
|
<%= t('spree.apply_code') %>
|
@@ -27,7 +27,9 @@
|
|
27
27
|
<% ship_form.object.manifest.each do |item| %>
|
28
28
|
<tr class="stock-item">
|
29
29
|
<td class="item-image">
|
30
|
-
<%= render 'spree/shared/image',
|
30
|
+
<%= render 'spree/shared/image',
|
31
|
+
image: (item.variant.gallery.images.first || item.variant.product.gallery.images.first),
|
32
|
+
size: :mini %>
|
31
33
|
</td>
|
32
34
|
<td class="item-name"><%= item.variant.name %></td>
|
33
35
|
<td class="item-qty"><%= item.quantity %></td>
|
@@ -75,7 +77,9 @@
|
|
75
77
|
<% @differentiator.missing.each do |variant, quantity| %>
|
76
78
|
<tr class="stock-item">
|
77
79
|
<td class="item-image">
|
78
|
-
<%= render 'spree/shared/image',
|
80
|
+
<%= render 'spree/shared/image',
|
81
|
+
image: (variant.gallery.images.first || variant.product.gallery.images.first),
|
82
|
+
size: :mini %>
|
79
83
|
</td>
|
80
84
|
<td class="item-name"><%= variant.name %></td>
|
81
85
|
<td class="item-qty"><%= quantity %></td>
|
@@ -15,7 +15,7 @@
|
|
15
15
|
</tbody>
|
16
16
|
<% if order.adjustments.nonzero.exists? || order.line_item_adjustments.nonzero.exists? || order.shipment_adjustments.nonzero.exists? || order.shipments.any? %>
|
17
17
|
<tr class="cart-subtotal">
|
18
|
-
<td colspan="4" align='right'><h5><%= t('spree.cart_subtotal', count: order.line_items.sum(:quantity)) %></h5></
|
18
|
+
<td colspan="4" align='right'><h5><%= t('spree.cart_subtotal', count: order.line_items.sum(:quantity)) %></h5></td>
|
19
19
|
<td colspan><h5><%= order.display_item_total %></h5></td>
|
20
20
|
<td></td>
|
21
21
|
</tr>
|
@@ -2,11 +2,9 @@
|
|
2
2
|
<%= order_form.fields_for :line_items, line_item do |item_form| -%>
|
3
3
|
<tr class="<%= cycle('', 'alt') %> line-item">
|
4
4
|
<td class="cart-item-image" data-hook="cart_item_image">
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
<%= link_to(render('spree/shared/image', image: variant.images.first, size: :small), variant.product) %>
|
9
|
-
<% end %>
|
5
|
+
<%= link_to(render('spree/shared/image',
|
6
|
+
image: (variant.gallery.images.first || variant.product.gallery.images.first),
|
7
|
+
size: :small), variant.product) %>
|
10
8
|
</td>
|
11
9
|
<td class="cart-item-description" data-hook="cart_item_description">
|
12
10
|
<h4><%= link_to line_item.name, product_path(variant.product) %></h4>
|
@@ -16,19 +16,16 @@
|
|
16
16
|
<div data-hook="inside_cart_form">
|
17
17
|
|
18
18
|
<div data-hook="cart_items">
|
19
|
-
<%= render 'form', order_form: order_form %>
|
19
|
+
<%= render 'spree/orders/form', order_form: order_form %>
|
20
20
|
</div>
|
21
21
|
|
22
22
|
<div class="links columns sixteen alpha omega" data-hook="cart_buttons">
|
23
|
-
<%=
|
24
|
-
|
25
|
-
<%= t('spree.update') %>
|
26
|
-
<% end %>
|
23
|
+
<%= button_tag t("spree.update"), class: "primary", id: "update-button" %>
|
24
|
+
|
27
25
|
<%= button_tag class: 'button checkout primary', id: 'checkout-link', name: 'checkout' do %>
|
28
26
|
<%= t('spree.checkout') %>
|
29
27
|
<% end %>
|
30
28
|
</div>
|
31
|
-
|
32
29
|
</div>
|
33
30
|
<% end %>
|
34
31
|
</div>
|
@@ -41,6 +38,8 @@
|
|
41
38
|
<%= link_to t('spree.continue_shopping'), products_path, class: 'continue button gray' %>
|
42
39
|
</p>
|
43
40
|
<% end %>
|
41
|
+
|
42
|
+
<%= render template: 'spree/coupon_codes/new' %>
|
44
43
|
</div>
|
45
44
|
|
46
45
|
<% end %>
|
@@ -1,5 +1,8 @@
|
|
1
1
|
<% if defined?(image) && image %>
|
2
2
|
<%= render 'spree/shared/image', image: image, size: :product, itemprop: "image" %>
|
3
3
|
<% else %>
|
4
|
-
<%= render 'spree/shared/image',
|
4
|
+
<%= render 'spree/shared/image',
|
5
|
+
image: @product.gallery.images.first,
|
6
|
+
size: :product,
|
7
|
+
itemprop: "image" %>
|
5
8
|
<% end %>
|
@@ -1,17 +1,19 @@
|
|
1
1
|
<%# no need for thumbnails unless there is more than one image %>
|
2
|
-
<% if
|
2
|
+
<% if @product.gallery.images.size > 1 %>
|
3
3
|
<ul id="product-thumbnails" class="thumbnails inline" data-hook>
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
|
5
|
+
<% @product.gallery.images.each do |image| %>
|
6
|
+
<% next if image.viewable_id != @product.master.id %>
|
7
|
+
<li class='tmb-all tmb-<%= image.viewable_id %>'>
|
8
|
+
<%= link_to(image_tag(image.url(:mini)), image.url(:product)) %>
|
7
9
|
</li>
|
8
10
|
<% end %>
|
9
11
|
|
10
12
|
<% if @product.has_variants? %>
|
11
|
-
<% @product.
|
12
|
-
<% next if @product.
|
13
|
-
<li class='vtmb tmb-<%=
|
14
|
-
<%= link_to(image_tag(
|
13
|
+
<% @product.gallery.images.each do |image| %>
|
14
|
+
<% next if image.viewable_id == @product.master.id %>
|
15
|
+
<li class='vtmb tmb-<%= image.viewable_id %>'>
|
16
|
+
<%= link_to(image_tag(image.url(:mini)), image.url(:product)) %>
|
15
17
|
</li>
|
16
18
|
<% end %>
|
17
19
|
<% end %>
|
@@ -1,11 +1,11 @@
|
|
1
1
|
<% size ||= :mini %>
|
2
2
|
<% itemprop ||= nil %>
|
3
3
|
|
4
|
-
<% if
|
5
|
-
<% if
|
6
|
-
<%= image_tag
|
4
|
+
<% if image_url = image.try(:url, size) %>
|
5
|
+
<% if image_alt = image.try(:alt) %>
|
6
|
+
<%= image_tag image_url, alt: image_alt, itemprop: itemprop %>
|
7
7
|
<% else %>
|
8
|
-
<%= image_tag
|
8
|
+
<%= image_tag image_url, itemprop: itemprop %>
|
9
9
|
<% end %>
|
10
10
|
<% else %>
|
11
11
|
<span class="image-placeholder <%= size %>"></span>
|
@@ -62,11 +62,9 @@
|
|
62
62
|
<% order.line_items.each do |item| %>
|
63
63
|
<tr data-hook="order_details_line_item_row">
|
64
64
|
<td data-hook="order_item_image">
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
<%= link_to(render('spree/shared/image', image: item.variant.images.first, size: :small), item.variant.product) %>
|
69
|
-
<% end %>
|
65
|
+
<%= link_to(render('spree/shared/image',
|
66
|
+
image: (item.variant.gallery.images.first || item.variant.product.gallery.images.first),
|
67
|
+
size: :small), item.variant.product) %>
|
70
68
|
</td>
|
71
69
|
<td data-hook="order_item_description">
|
72
70
|
<h4><%= item.variant.product.name %></h4>
|
@@ -28,7 +28,7 @@
|
|
28
28
|
<li id="product_<%= product.id %>" class="columns three <%= cycle("alpha", "secondary", "", "omega secondary", name: "classes") %>" data-hook="products_list_item" itemscope itemtype="http://schema.org/Product">
|
29
29
|
<% cache(@taxon.present? ? [I18n.locale, current_pricing_options, @taxon, product] : [I18n.locale, current_pricing_options, product]) do %>
|
30
30
|
<div class="product-image">
|
31
|
-
<%= link_to(render('spree/shared/image', image: product.
|
31
|
+
<%= link_to(render('spree/shared/image', image: product.gallery.images.first, size: :small, itemprop: "image"), url, itemprop: 'url') %>
|
32
32
|
</div>
|
33
33
|
<%= link_to truncate(product.name, length: 50), url, class: 'info', itemprop: "name", title: product.name %>
|
34
34
|
<span itemprop="offers" itemscope itemtype="http://schema.org/Offer">
|
data/config/routes.rb
CHANGED
data/lib/spree/frontend.rb
CHANGED
data/solidus_frontend.gemspec
CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.add_dependency 'font-awesome-rails', '~> 4.0'
|
29
29
|
s.add_dependency 'jquery-rails'
|
30
30
|
s.add_dependency 'kaminari', '~> 1.1'
|
31
|
-
s.add_dependency '
|
31
|
+
s.add_dependency 'sassc-rails'
|
32
32
|
s.add_dependency 'truncate_html', '~> 0.9', '>= 0.9.2'
|
33
33
|
|
34
34
|
s.add_development_dependency 'capybara-accessible'
|
@@ -14,7 +14,6 @@ describe Spree::CheckoutController, type: :controller do
|
|
14
14
|
|
15
15
|
before do
|
16
16
|
allow(controller).to receive_messages try_spree_current_user: user
|
17
|
-
allow(controller).to receive_messages spree_current_user: user
|
18
17
|
allow(controller).to receive_messages current_order: order
|
19
18
|
end
|
20
19
|
|
@@ -90,7 +89,7 @@ describe Spree::CheckoutController, type: :controller do
|
|
90
89
|
order.line_items << FactoryBot.create(:line_item)
|
91
90
|
end
|
92
91
|
|
93
|
-
context "with the order in the cart state" do
|
92
|
+
context "with the order in the cart state", partial_double_verification: false do
|
94
93
|
before do
|
95
94
|
order.update_attributes! user: user
|
96
95
|
order.update_column(:state, "cart")
|
@@ -139,7 +138,7 @@ describe Spree::CheckoutController, type: :controller do
|
|
139
138
|
end
|
140
139
|
end
|
141
140
|
|
142
|
-
context "with the order in the address state" do
|
141
|
+
context "with the order in the address state", partial_double_verification: false do
|
143
142
|
before do
|
144
143
|
order.update_attributes! user: user
|
145
144
|
order.update_columns(ship_address_id: create(:address).id, state: "address")
|
@@ -152,7 +151,7 @@ describe Spree::CheckoutController, type: :controller do
|
|
152
151
|
end
|
153
152
|
end
|
154
153
|
|
155
|
-
context "with a billing and shipping address" do
|
154
|
+
context "with a billing and shipping address", partial_double_verification: false do
|
156
155
|
subject do
|
157
156
|
post :update, params: {
|
158
157
|
state: "address",
|
@@ -184,7 +183,7 @@ describe Spree::CheckoutController, type: :controller do
|
|
184
183
|
# the same thing here.
|
185
184
|
# Perhaps we can just remove 'set_payment_parameters_amount' entirely at
|
186
185
|
# some point?
|
187
|
-
context "when there is a checkout step between payment and confirm" do
|
186
|
+
context "when there is a checkout step between payment and confirm", partial_double_verification: false do
|
188
187
|
before do
|
189
188
|
@old_checkout_flow = Spree::Order.checkout_flow
|
190
189
|
Spree::Order.class_eval do
|
@@ -227,7 +226,7 @@ describe Spree::CheckoutController, type: :controller do
|
|
227
226
|
end
|
228
227
|
end
|
229
228
|
|
230
|
-
context "when in the payment state" do
|
229
|
+
context "when in the payment state", partial_double_verification: false do
|
231
230
|
let(:order) { create(:order_with_line_items) }
|
232
231
|
let(:payment_method) { create(:credit_card_payment_method) }
|
233
232
|
|
@@ -420,6 +419,45 @@ describe Spree::CheckoutController, type: :controller do
|
|
420
419
|
expect(flash[:error]).to eq(I18n.t('spree.payment_processing_failed'))
|
421
420
|
end
|
422
421
|
end
|
422
|
+
|
423
|
+
context "when InsufficientStock error is raised" do
|
424
|
+
before do
|
425
|
+
allow(controller).to receive_messages current_order: order
|
426
|
+
allow(controller).to receive_messages check_authorization: true
|
427
|
+
allow(controller).to receive_messages ensure_sufficient_stock_lines: true
|
428
|
+
end
|
429
|
+
|
430
|
+
context "when the order has no shipments" do
|
431
|
+
let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:address) }
|
432
|
+
|
433
|
+
before do
|
434
|
+
allow(order).to receive_messages shipments: []
|
435
|
+
# Order#next is the tipical failure point here:
|
436
|
+
allow(order).to receive(:next).and_raise(Spree::Order::InsufficientStock)
|
437
|
+
end
|
438
|
+
|
439
|
+
it "redirects the customer to the cart page with an error message" do
|
440
|
+
put :update, params: { state: order.state, order: {} }
|
441
|
+
expect(flash[:error]).to eq(I18n.t('spree.insufficient_stock_for_order'))
|
442
|
+
expect(response).to redirect_to(spree.cart_path)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
context "when the order has shipments" do
|
447
|
+
let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
|
448
|
+
|
449
|
+
context "when items become somehow not available anymore" do
|
450
|
+
before { Spree::StockItem.update_all backorderable: false }
|
451
|
+
|
452
|
+
it "redirects the customer to the address checkout page with an error message" do
|
453
|
+
put :update, params: { state: order.state, order: {} }
|
454
|
+
error = I18n.t('spree.inventory_error_flash_for_insufficient_shipment_quantity', unavailable_items: order.products.first.name)
|
455
|
+
expect(flash[:error]).to eq(error)
|
456
|
+
expect(response).to redirect_to(spree.checkout_state_path(state: :address))
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
423
461
|
end
|
424
462
|
|
425
463
|
context "When last inventory item has been purchased" do
|
@@ -501,6 +539,8 @@ describe Spree::CheckoutController, type: :controller do
|
|
501
539
|
let(:promotion_handler) { instance_double('Spree::PromotionHandler::Coupon', error: nil, success: 'Coupon Applied!') }
|
502
540
|
|
503
541
|
it "continues checkout flow normally" do
|
542
|
+
expect(Spree::Deprecation).to receive(:warn)
|
543
|
+
|
504
544
|
expect(Spree::PromotionHandler::Coupon)
|
505
545
|
.to receive_message_chain(:new, :apply)
|
506
546
|
.and_return(promotion_handler)
|
@@ -515,6 +555,8 @@ describe Spree::CheckoutController, type: :controller do
|
|
515
555
|
let(:promotion_handler) { instance_double('Spree::PromotionHandler::Coupon', error: 'Some error', success: false) }
|
516
556
|
|
517
557
|
it "setups the current step correctly before rendering" do
|
558
|
+
expect(Spree::Deprecation).to receive(:warn)
|
559
|
+
|
518
560
|
expect(Spree::PromotionHandler::Coupon)
|
519
561
|
.to receive_message_chain(:new, :apply)
|
520
562
|
.and_return(promotion_handler)
|
@@ -524,6 +566,8 @@ describe Spree::CheckoutController, type: :controller do
|
|
524
566
|
end
|
525
567
|
|
526
568
|
it "render cart with coupon error" do
|
569
|
+
expect(Spree::Deprecation).to receive(:warn)
|
570
|
+
|
527
571
|
expect(Spree::PromotionHandler::Coupon)
|
528
572
|
.to receive_message_chain(:new, :apply)
|
529
573
|
.and_return(promotion_handler)
|
@@ -7,8 +7,6 @@ module Spree
|
|
7
7
|
ORDER_TOKEN = 'ORDER_TOKEN'
|
8
8
|
|
9
9
|
let!(:store) { create(:store) }
|
10
|
-
let(:user) { create(:user) }
|
11
|
-
let(:guest_user) { create(:user) }
|
12
10
|
let(:order) { Spree::Order.create }
|
13
11
|
let(:variant) { create(:variant) }
|
14
12
|
|
@@ -21,7 +19,6 @@ module Spree
|
|
21
19
|
|
22
20
|
before do
|
23
21
|
allow(controller).to receive_messages current_order: order
|
24
|
-
allow(controller).to receive_messages spree_current_user: user
|
25
22
|
end
|
26
23
|
|
27
24
|
context '#populate' do
|
@@ -149,6 +149,8 @@ describe Spree::OrdersController, type: :controller do
|
|
149
149
|
let(:promotion_handler) { instance_double('Spree::PromotionHandler::Coupon', error: nil, success: 'Coupon Applied!') }
|
150
150
|
|
151
151
|
it "continues checkout flow normally" do
|
152
|
+
expect(Spree::Deprecation).to receive(:warn)
|
153
|
+
|
152
154
|
expect(Spree::PromotionHandler::Coupon)
|
153
155
|
.to receive_message_chain(:new, :apply)
|
154
156
|
.and_return(promotion_handler)
|
@@ -163,6 +165,8 @@ describe Spree::OrdersController, type: :controller do
|
|
163
165
|
let(:promotion_handler) { instance_double('Spree::PromotionHandler::Coupon', error: 'Some error', success: false) }
|
164
166
|
|
165
167
|
it "render cart with coupon error" do
|
168
|
+
expect(Spree::Deprecation).to receive(:warn)
|
169
|
+
|
166
170
|
expect(Spree::PromotionHandler::Coupon)
|
167
171
|
.to receive_message_chain(:new, :apply)
|
168
172
|
.and_return(promotion_handler)
|
@@ -7,7 +7,7 @@ describe Spree::ProductsController, type: :controller do
|
|
7
7
|
|
8
8
|
# Regression test for https://github.com/spree/spree/issues/1390
|
9
9
|
it "allows admins to view non-active products" do
|
10
|
-
allow(controller).to receive_messages
|
10
|
+
allow(controller).to receive_messages try_spree_current_user: mock_model(Spree.user_class, has_spree_role?: true, last_incomplete_spree_order: nil, spree_api_key: 'fake')
|
11
11
|
get :show, params: { id: product.to_param }
|
12
12
|
expect(response.status).to eq(200)
|
13
13
|
end
|
@@ -20,7 +20,7 @@ describe Spree::ProductsController, type: :controller do
|
|
20
20
|
|
21
21
|
it "should provide the current user to the searcher class" do
|
22
22
|
user = mock_model(Spree.user_class, last_incomplete_spree_order: nil, spree_api_key: 'fake')
|
23
|
-
allow(controller).to receive_messages
|
23
|
+
allow(controller).to receive_messages try_spree_current_user: user
|
24
24
|
expect_any_instance_of(Spree::Config.searcher_class).to receive(:current_user=).with(user)
|
25
25
|
get :index
|
26
26
|
expect(response.status).to eq(200)
|
@@ -29,7 +29,7 @@ describe Spree::ProductsController, type: :controller do
|
|
29
29
|
# Regression test for https://github.com/spree/spree/issues/2249
|
30
30
|
it "doesn't error when given an invalid referer" do
|
31
31
|
current_user = mock_model(Spree.user_class, has_spree_role?: true, last_incomplete_spree_order: nil, generate_spree_api_key!: nil)
|
32
|
-
allow(controller).to receive_messages
|
32
|
+
allow(controller).to receive_messages try_spree_current_user: current_user
|
33
33
|
request.env['HTTP_REFERER'] = "not|a$url"
|
34
34
|
|
35
35
|
# Previously a URI::InvalidURIError exception was being thrown
|
@@ -6,7 +6,7 @@ describe Spree::TaxonsController, type: :controller do
|
|
6
6
|
it "should provide the current user to the searcher class" do
|
7
7
|
taxon = create(:taxon, permalink: "test")
|
8
8
|
user = mock_model(Spree.user_class, last_incomplete_spree_order: nil, spree_api_key: 'fake')
|
9
|
-
allow(controller).to receive_messages
|
9
|
+
allow(controller).to receive_messages try_spree_current_user: user
|
10
10
|
expect_any_instance_of(Spree::Config.searcher_class).to receive(:current_user=).with(user)
|
11
11
|
get :show, params: { id: taxon.permalink }
|
12
12
|
expect(response.status).to eq(200)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe "Checkout confirm page submission", type: :feature do
|
6
|
+
include_context 'checkout setup'
|
7
|
+
|
8
|
+
context "when the product from the order is not backorderable but has enough stock quantity" do
|
9
|
+
let(:user) { create(:user) }
|
10
|
+
|
11
|
+
let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
|
12
|
+
let(:order_product) { order.products.first }
|
13
|
+
let(:order_stock_item) { order_product.stock_items.first }
|
14
|
+
|
15
|
+
before do
|
16
|
+
order_stock_item.update! backorderable: false
|
17
|
+
order_stock_item.set_count_on_hand(1)
|
18
|
+
allow_any_instance_of(Spree::CheckoutController).to receive_messages(current_order: order)
|
19
|
+
allow_any_instance_of(Spree::CheckoutController).to receive_messages(try_spree_current_user: user)
|
20
|
+
allow_any_instance_of(Spree::OrdersController).to receive_messages(try_spree_current_user: user)
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when there are not other backorderable stock locations' do
|
24
|
+
context 'when the customer is on the confirm page and the availabilty drops to zero' do
|
25
|
+
before do
|
26
|
+
visit spree.checkout_state_path(:confirm)
|
27
|
+
order_stock_item.set_count_on_hand(0)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'redirects to cart page and shows an unavailable product message' do
|
31
|
+
click_button "Place Order"
|
32
|
+
expect(page).to have_content "#{order_product.name} became unavailable"
|
33
|
+
expect(page).to have_current_path spree.cart_path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when there is another backorderable stock location' do
|
39
|
+
before do
|
40
|
+
create :stock_location, backorderable_default: true, default: false
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when the customer is on the confirm page and the availabilty drops to zero' do
|
44
|
+
before do
|
45
|
+
visit spree.checkout_state_path(:confirm)
|
46
|
+
order_stock_item.set_count_on_hand(0)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "redirects to the address checkout page and shows an availability changed message" do
|
50
|
+
click_button "Place Order"
|
51
|
+
error_message = "Quantity selected of #{order_product.name} is not available. Still, items may be available from another stock location, please try again."
|
52
|
+
expect(page).to have_content error_message
|
53
|
+
expect(page).to have_current_path spree.checkout_state_path(:address)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "can still complete the order using the backorderable stock location by restarting the checkout" do
|
57
|
+
click_button "Place Order"
|
58
|
+
expect(page).to have_current_path spree.checkout_state_path(:address)
|
59
|
+
click_button "Save and Continue"
|
60
|
+
expect(page).to have_current_path spree.checkout_state_path(:delivery)
|
61
|
+
click_button "Save and Continue"
|
62
|
+
expect(page).to have_current_path spree.checkout_state_path(:payment)
|
63
|
+
click_button "Save and Continue"
|
64
|
+
expect(page).to have_current_path spree.checkout_state_path(:confirm)
|
65
|
+
click_button "Place Order"
|
66
|
+
expect(page).to have_content 'Your order has been processed successfully'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -350,7 +350,7 @@ describe "Checkout", type: :feature, inaccessible: true do
|
|
350
350
|
choose "use_existing_card_no"
|
351
351
|
|
352
352
|
fill_in "Name on card", with: 'Spree Commerce'
|
353
|
-
|
353
|
+
fill_in_with_force "Card Number", with: '4111 1111 1111 1111'
|
354
354
|
fill_in "card_expiry", with: '04 / 20'
|
355
355
|
fill_in "Card Code", with: '123'
|
356
356
|
|
@@ -130,22 +130,22 @@ describe "Coupon code promotions", type: :feature, js: true do
|
|
130
130
|
end
|
131
131
|
|
132
132
|
it "can enter a coupon code and receives success notification" do
|
133
|
-
fill_in "
|
134
|
-
click_button "
|
133
|
+
fill_in "coupon_code", with: "onetwo"
|
134
|
+
click_button "Apply Code"
|
135
135
|
expect(page).to have_content(I18n.t('spree.coupon_code_applied'))
|
136
136
|
end
|
137
137
|
|
138
138
|
it "can enter a promotion code with both upper and lower case letters" do
|
139
|
-
fill_in "
|
140
|
-
click_button "
|
139
|
+
fill_in "coupon_code", with: "ONETwO"
|
140
|
+
click_button "Apply Code"
|
141
141
|
expect(page).to have_content(I18n.t('spree.coupon_code_applied'))
|
142
142
|
end
|
143
143
|
|
144
144
|
it "informs the user about a coupon code which has exceeded its usage" do
|
145
145
|
expect_any_instance_of(Spree::Promotion).to receive(:usage_limit_exceeded?).and_return(true)
|
146
146
|
|
147
|
-
fill_in "
|
148
|
-
click_button "
|
147
|
+
fill_in "coupon_code", with: "onetwo"
|
148
|
+
click_button "Apply Code"
|
149
149
|
expect(page).to have_content(I18n.t('spree.coupon_code_max_usage'))
|
150
150
|
end
|
151
151
|
|
@@ -160,8 +160,8 @@ describe "Coupon code promotions", type: :feature, js: true do
|
|
160
160
|
specify do
|
161
161
|
visit spree.cart_path
|
162
162
|
|
163
|
-
fill_in "
|
164
|
-
click_button "
|
163
|
+
fill_in "coupon_code", with: "onetwo"
|
164
|
+
click_button "Apply Code"
|
165
165
|
expect(page).to have_content(I18n.t(:item_total_less_than_or_equal, scope: [:spree, :eligibility_errors, :messages], amount: "$100.00"))
|
166
166
|
end
|
167
167
|
end
|
@@ -170,8 +170,8 @@ describe "Coupon code promotions", type: :feature, js: true do
|
|
170
170
|
promotion.expires_at = Date.today.beginning_of_week
|
171
171
|
promotion.starts_at = Date.today.beginning_of_week.advance(day: 3)
|
172
172
|
promotion.save!
|
173
|
-
fill_in "
|
174
|
-
click_button "
|
173
|
+
fill_in "coupon_code", with: "onetwo"
|
174
|
+
click_button "Apply Code"
|
175
175
|
expect(page).to have_content(I18n.t('spree.coupon_code_expired'))
|
176
176
|
end
|
177
177
|
|
@@ -191,8 +191,8 @@ describe "Coupon code promotions", type: :feature, js: true do
|
|
191
191
|
click_button "add-to-cart-button"
|
192
192
|
|
193
193
|
visit spree.cart_path
|
194
|
-
fill_in "
|
195
|
-
click_button "
|
194
|
+
fill_in "coupon_code", with: "onetwo"
|
195
|
+
click_button "Apply Code"
|
196
196
|
|
197
197
|
fill_in "order_line_items_attributes_0_quantity", with: 2
|
198
198
|
fill_in "order_line_items_attributes_1_quantity", with: 2
|
@@ -237,8 +237,8 @@ describe "Coupon code promotions", type: :feature, js: true do
|
|
237
237
|
expect(page).to have_content("$30.00")
|
238
238
|
end
|
239
239
|
|
240
|
-
fill_in "
|
241
|
-
click_button "
|
240
|
+
fill_in "coupon_code", with: "onetwo"
|
241
|
+
click_button "Apply Code"
|
242
242
|
|
243
243
|
within '#cart_adjustments' do
|
244
244
|
expect(page).to have_content("Promotion (Onetwo) -$30.00")
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
RSpec.feature "Promotion Code Invalidation" do
|
5
|
+
RSpec.feature "Promotion Code Invalidation", js: true do
|
6
6
|
given!(:promotion) do
|
7
7
|
FactoryBot.create(
|
8
8
|
:promotion_with_item_adjustment,
|
@@ -28,7 +28,7 @@ RSpec.feature "Promotion Code Invalidation" do
|
|
28
28
|
|
29
29
|
scenario "adding the promotion to a cart with two applicable items" do
|
30
30
|
fill_in "Coupon code", with: "PROMO"
|
31
|
-
click_button "
|
31
|
+
click_button "Apply Code"
|
32
32
|
|
33
33
|
expect(page).to have_content("The coupon code was successfully applied to your order")
|
34
34
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
RSpec.feature "Quantity Promotions" do
|
5
|
+
RSpec.feature "Quantity Promotions", js: true do
|
6
6
|
given(:action) do
|
7
7
|
Spree::Promotion::Actions::CreateQuantityAdjustments.create(
|
8
8
|
calculator: calculator,
|
@@ -26,8 +26,8 @@ RSpec.feature "Quantity Promotions" do
|
|
26
26
|
|
27
27
|
scenario "adding and removing items from the cart" do
|
28
28
|
# Attempt to use the code with too few items.
|
29
|
-
fill_in "
|
30
|
-
click_button "
|
29
|
+
fill_in "coupon_code", with: "PROMO"
|
30
|
+
click_button "Apply Code"
|
31
31
|
expect(page).to have_content("This coupon code could not be applied to the cart at this time")
|
32
32
|
|
33
33
|
# Add another item to our cart.
|
@@ -36,8 +36,8 @@ RSpec.feature "Quantity Promotions" do
|
|
36
36
|
click_button "Add To Cart"
|
37
37
|
|
38
38
|
# Using the code should now succeed.
|
39
|
-
fill_in "
|
40
|
-
click_button "
|
39
|
+
fill_in "coupon_code", with: "PROMO"
|
40
|
+
click_button "Apply Code"
|
41
41
|
expect(page).to have_content("The coupon code was successfully applied to your order")
|
42
42
|
within("#cart_adjustments") do
|
43
43
|
expect(page).to have_content("-$10.00")
|
@@ -70,8 +70,8 @@ RSpec.feature "Quantity Promotions" do
|
|
70
70
|
click_button "Update"
|
71
71
|
|
72
72
|
# Apply the promo code and see a $10 discount (for 2 of the 3 items)
|
73
|
-
fill_in "
|
74
|
-
click_button "
|
73
|
+
fill_in "coupon_code", with: "PROMO"
|
74
|
+
click_button "Apply Code"
|
75
75
|
expect(page).to have_content("The coupon code was successfully applied to your order")
|
76
76
|
within("#cart_adjustments") do
|
77
77
|
expect(page).to have_content("-$10.00")
|
@@ -104,8 +104,8 @@ RSpec.feature "Quantity Promotions" do
|
|
104
104
|
click_button "Update"
|
105
105
|
|
106
106
|
# Apply the promo code and see a $15 discount
|
107
|
-
fill_in "
|
108
|
-
click_button "
|
107
|
+
fill_in "coupon_code", with: "PROMO"
|
108
|
+
click_button "Apply Code"
|
109
109
|
expect(page).to have_content("The coupon code was successfully applied to your order")
|
110
110
|
within("#cart_adjustments") do
|
111
111
|
expect(page).to have_content("-$15.00")
|
data/spec/spec_helper.rb
CHANGED
@@ -26,6 +26,7 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
|
26
26
|
|
27
27
|
require 'database_cleaner'
|
28
28
|
|
29
|
+
require 'spree/testing_support/partial_double_verification'
|
29
30
|
require 'spree/testing_support/authorization_helpers'
|
30
31
|
require 'spree/testing_support/capybara_ext'
|
31
32
|
require 'spree/testing_support/factories'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FillInWithForce
|
4
|
+
def fill_in_with_force(locator, with:)
|
5
|
+
field_id = find_field(locator)[:id]
|
6
|
+
page.execute_script "document.getElementById('#{field_id}').value = '#{with}';"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.include FillInWithForce, type: :feature
|
12
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solidus_frontend
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Solidus Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: solidus_api
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
19
|
+
version: 2.8.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
26
|
+
version: 2.8.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: solidus_core
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - '='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.
|
33
|
+
version: 2.8.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 2.
|
40
|
+
version: 2.8.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: canonical-rails
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,7 +95,7 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '1.1'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: sassc-rails
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
@@ -195,6 +195,7 @@ files:
|
|
195
195
|
- app/assets/stylesheets/spree/frontend/screen.css.scss
|
196
196
|
- app/controllers/spree/checkout_controller.rb
|
197
197
|
- app/controllers/spree/content_controller.rb
|
198
|
+
- app/controllers/spree/coupon_codes_controller.rb
|
198
199
|
- app/controllers/spree/home_controller.rb
|
199
200
|
- app/controllers/spree/locale_controller.rb
|
200
201
|
- app/controllers/spree/orders_controller.rb
|
@@ -218,6 +219,7 @@ files:
|
|
218
219
|
- app/views/spree/checkout/payment/_check.html.erb
|
219
220
|
- app/views/spree/checkout/payment/_gateway.html.erb
|
220
221
|
- app/views/spree/content/cvv.html.erb
|
222
|
+
- app/views/spree/coupon_codes/new.html.erb
|
221
223
|
- app/views/spree/home/index.html.erb
|
222
224
|
- app/views/spree/layouts/spree_application.html.erb
|
223
225
|
- app/views/spree/orders/_adjustment_row.html.erb
|
@@ -286,6 +288,7 @@ files:
|
|
286
288
|
- spec/features/caching/products_spec.rb
|
287
289
|
- spec/features/caching/taxons_spec.rb
|
288
290
|
- spec/features/cart_spec.rb
|
291
|
+
- spec/features/checkout_confirm_insufficient_stock_spec.rb
|
289
292
|
- spec/features/checkout_spec.rb
|
290
293
|
- spec/features/checkout_unshippable_spec.rb
|
291
294
|
- spec/features/coupon_code_spec.rb
|
@@ -304,6 +307,7 @@ files:
|
|
304
307
|
- spec/helpers/order_helper_spec.rb
|
305
308
|
- spec/helpers/taxon_filters_helper_spec.rb
|
306
309
|
- spec/spec_helper.rb
|
310
|
+
- spec/support/features/fill_in_with_force.rb
|
307
311
|
- spec/support/shared_contexts/checkout_setup.rb
|
308
312
|
- spec/support/shared_contexts/custom_products.rb
|
309
313
|
- spec/support/shared_contexts/locales.rb
|
@@ -328,7 +332,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
328
332
|
version: 1.8.23
|
329
333
|
requirements:
|
330
334
|
- none
|
331
|
-
|
335
|
+
rubyforge_project:
|
336
|
+
rubygems_version: 2.7.3
|
332
337
|
signing_key:
|
333
338
|
specification_version: 4
|
334
339
|
summary: Cart and storefront for the Solidus e-commerce project.
|