spree_core 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/app/helpers/spree/base_helper.rb +5 -7
- data/app/jobs/spree/payments/handle_webhook_job.rb +22 -0
- data/app/mailers/spree/base_mailer.rb +1 -1
- data/app/models/concerns/spree/user_methods.rb +16 -0
- data/app/models/spree/allowed_origin.rb +44 -0
- data/app/models/spree/asset.rb +121 -3
- data/app/models/spree/cart_promotion.rb +7 -0
- data/app/models/spree/checkout/default_requirements.rb +51 -0
- data/app/models/spree/checkout/registry.rb +112 -0
- data/app/models/spree/checkout/requirement.rb +49 -0
- data/app/models/spree/checkout/requirements.rb +56 -0
- data/app/models/spree/checkout/step.rb +52 -0
- data/app/models/spree/image/configuration/active_storage.rb +2 -14
- data/app/models/spree/image.rb +2 -78
- data/app/models/spree/legacy_user.rb +1 -0
- data/app/models/spree/line_item.rb +3 -3
- data/app/models/spree/order/checkout.rb +18 -0
- data/app/models/spree/order.rb +3 -0
- data/app/models/spree/order_promotion.rb +2 -0
- data/app/models/spree/payment_method.rb +34 -0
- data/app/models/spree/payment_session.rb +18 -0
- data/app/models/spree/product.rb +45 -34
- data/app/models/spree/shipment.rb +1 -0
- data/app/models/spree/store.rb +32 -0
- data/app/models/spree/variant.rb +21 -12
- data/app/services/spree/cart/create.rb +3 -30
- data/app/services/spree/carts/complete.rb +46 -0
- data/app/services/spree/carts/create.rb +32 -0
- data/app/services/spree/carts/update.rb +115 -0
- data/app/services/spree/{cart → carts}/upsert_items.rb +19 -23
- data/app/services/spree/payments/handle_webhook.rb +58 -0
- data/app/services/spree/seeds/all.rb +1 -0
- data/app/services/spree/seeds/allowed_origins.rb +14 -0
- data/app/views/spree/shared/_mailer_logo.html.erb +1 -1
- data/config/locales/en.yml +23 -2
- data/db/migrate/20260315000000_create_spree_allowed_origins.rb +14 -0
- data/db/migrate/20260315100000_add_product_media_support.rb +21 -0
- data/lib/spree/core/configuration.rb +3 -0
- data/lib/spree/core/dependencies.rb +4 -2
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +1 -0
- data/lib/spree/permitted_attributes.rb +5 -1
- data/lib/spree/testing_support/factories/allowed_origin_factory.rb +8 -0
- data/lib/spree/testing_support/factories/asset_factory.rb +6 -9
- data/lib/spree/testing_support/factories/image_factory.rb +3 -1
- data/lib/spree/testing_support/factories/order_factory.rb +3 -0
- data/lib/tasks/images.rake +11 -11
- data/lib/tasks/products.rake +4 -2
- metadata +21 -6
- data/app/services/spree/orders/update.rb +0 -121
data/app/models/spree/image.rb
CHANGED
|
@@ -1,82 +1,6 @@
|
|
|
1
|
+
# Backward compatibility — all logic now lives in Spree::Asset.
|
|
2
|
+
# This class will be removed in Spree 6.0.
|
|
1
3
|
module Spree
|
|
2
4
|
class Image < Asset
|
|
3
|
-
include Spree::Image::Configuration::ActiveStorage # legacy to be removed in Spree 6
|
|
4
|
-
include Rails.application.routes.url_helpers
|
|
5
|
-
include Spree::ImageMethods # legacy, will be removed in Spree 6
|
|
6
|
-
|
|
7
|
-
validates :attachment, attached: true, content_type: Rails.application.config.active_storage.web_image_content_types
|
|
8
|
-
|
|
9
|
-
after_commit :touch_product_variants, if: :should_touch_product_variants?, on: :update
|
|
10
|
-
after_commit :update_variant_thumbnail, on: [:create, :destroy]
|
|
11
|
-
after_commit :update_variant_thumbnail_on_reorder, on: :update, if: :saved_change_to_position?
|
|
12
|
-
after_commit :update_variant_thumbnail_on_viewable_change, on: :update, if: :saved_change_to_viewable_id?
|
|
13
|
-
|
|
14
|
-
after_create :increment_viewable_image_count
|
|
15
|
-
after_destroy :decrement_viewable_image_count
|
|
16
|
-
|
|
17
|
-
# In Rails 5.x class constants are being undefined/redefined during the code reloading process
|
|
18
|
-
# in a rails development environment, after which the actual ruby objects stored in those class constants
|
|
19
|
-
# are no longer equal (subclass == self) what causes error ActiveRecord::SubclassNotFound
|
|
20
|
-
# Invalid single-table inheritance type: Spree::Image is not a subclass of Spree::Image.
|
|
21
|
-
# The line below prevents the error.
|
|
22
|
-
self.inheritance_column = nil
|
|
23
|
-
|
|
24
|
-
# @deprecated
|
|
25
|
-
def styles
|
|
26
|
-
Spree::Deprecation.warn("Image#styles is deprecated and will be removed in Spree 6.0. Please use active storage variants with cdn_image_url")
|
|
27
|
-
|
|
28
|
-
self.class.styles.map do |_, size|
|
|
29
|
-
width, height = size.chop.split('x').map(&:to_i)
|
|
30
|
-
|
|
31
|
-
{
|
|
32
|
-
url: generate_url(size: size),
|
|
33
|
-
size: size,
|
|
34
|
-
width: width,
|
|
35
|
-
height: height
|
|
36
|
-
}
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
def touch_product_variants
|
|
43
|
-
viewable.product.variants.touch_all
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def should_touch_product_variants?
|
|
47
|
-
viewable.is_a?(Spree::Variant) &&
|
|
48
|
-
viewable.is_master? &&
|
|
49
|
-
viewable.product.has_variants? &&
|
|
50
|
-
saved_change_to_position?
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def increment_viewable_image_count
|
|
54
|
-
return unless viewable.is_a?(Spree::Variant)
|
|
55
|
-
|
|
56
|
-
Spree::Variant.increment_counter(:image_count, viewable_id)
|
|
57
|
-
Spree::Product.increment_counter(:total_image_count, viewable.product_id)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def decrement_viewable_image_count
|
|
61
|
-
return unless viewable.is_a?(Spree::Variant)
|
|
62
|
-
|
|
63
|
-
Spree::Variant.decrement_counter(:image_count, viewable_id)
|
|
64
|
-
Spree::Product.decrement_counter(:total_image_count, viewable.product_id)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def update_variant_thumbnail
|
|
68
|
-
return unless viewable.is_a?(Spree::Variant)
|
|
69
|
-
|
|
70
|
-
viewable.update_thumbnail!
|
|
71
|
-
viewable.product.update_thumbnail!
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def update_variant_thumbnail_on_reorder
|
|
75
|
-
update_variant_thumbnail
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def update_variant_thumbnail_on_viewable_change
|
|
79
|
-
update_variant_thumbnail
|
|
80
|
-
end
|
|
81
5
|
end
|
|
82
6
|
end
|
|
@@ -55,10 +55,10 @@ module Spree
|
|
|
55
55
|
delegate :name, :description, :brand, :category, to: :product
|
|
56
56
|
|
|
57
57
|
# Returns the thumbnail image for this line item
|
|
58
|
-
# Prefers variant
|
|
59
|
-
# @return [Spree::
|
|
58
|
+
# Prefers variant primary media, falls back to product primary media
|
|
59
|
+
# @return [Spree::Asset, nil]
|
|
60
60
|
def thumbnail
|
|
61
|
-
variant.
|
|
61
|
+
variant.primary_media || product.primary_media
|
|
62
62
|
end
|
|
63
63
|
delegate :tax_zone, to: :order
|
|
64
64
|
delegate :digital?, :can_supply?, to: :variant
|
|
@@ -227,6 +227,24 @@ module Spree
|
|
|
227
227
|
self.checkout_steps.index(step).to_i
|
|
228
228
|
end
|
|
229
229
|
|
|
230
|
+
# Customer-facing checkout step derived from the internal state machine state.
|
|
231
|
+
# Maps +'cart'+ to +'address'+ since cart is not a user-facing checkout step.
|
|
232
|
+
#
|
|
233
|
+
# @return [String] current checkout step name (e.g. +"address"+, +"delivery"+, +"payment"+)
|
|
234
|
+
def current_checkout_step
|
|
235
|
+
state == 'cart' ? 'address' : state
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Checkout steps that have already been completed, i.e. all steps before
|
|
239
|
+
# {#current_checkout_step}. Does not include +'complete'+.
|
|
240
|
+
#
|
|
241
|
+
# @return [Array<String>] completed step names in order
|
|
242
|
+
def completed_checkout_steps
|
|
243
|
+
steps = checkout_steps.reject { |s| s == 'complete' }
|
|
244
|
+
idx = steps.index(current_checkout_step) || 0
|
|
245
|
+
steps.first(idx)
|
|
246
|
+
end
|
|
247
|
+
|
|
230
248
|
def can_go_to_state?(state)
|
|
231
249
|
return false unless has_checkout_step?(self.state) && has_checkout_step?(state)
|
|
232
250
|
|
data/app/models/spree/order.rb
CHANGED
|
@@ -139,6 +139,7 @@ module Spree
|
|
|
139
139
|
inverse_of: :order
|
|
140
140
|
|
|
141
141
|
has_many :order_promotions, class_name: 'Spree::OrderPromotion'
|
|
142
|
+
has_many :cart_promotions, class_name: 'Spree::CartPromotion', foreign_key: :order_id
|
|
142
143
|
has_many :promotions, through: :order_promotions, class_name: 'Spree::Promotion'
|
|
143
144
|
|
|
144
145
|
has_many :shipments, class_name: 'Spree::Shipment', dependent: :destroy, inverse_of: :order do
|
|
@@ -148,6 +149,8 @@ module Spree
|
|
|
148
149
|
end
|
|
149
150
|
has_many :shipment_adjustments, through: :shipments, source: :adjustments
|
|
150
151
|
|
|
152
|
+
alias items line_items
|
|
153
|
+
|
|
151
154
|
accepts_nested_attributes_for :line_items
|
|
152
155
|
accepts_nested_attributes_for :bill_address
|
|
153
156
|
accepts_nested_attributes_for :ship_address
|
|
@@ -71,10 +71,44 @@ module Spree
|
|
|
71
71
|
|
|
72
72
|
# Completes a payment session via the provider.
|
|
73
73
|
# Override in gateway subclasses to implement provider-specific session completion.
|
|
74
|
+
#
|
|
75
|
+
# Responsibilities:
|
|
76
|
+
# - Verify payment status with the provider
|
|
77
|
+
# - Create/update the Spree::Payment record
|
|
78
|
+
# - Patch order data from provider (e.g. wallet billing address)
|
|
79
|
+
# - Transition payment session to completed/failed
|
|
80
|
+
#
|
|
81
|
+
# Must NOT complete the order — that is handled by Carts::Complete
|
|
82
|
+
# (called by the frontend or by the webhook handler).
|
|
74
83
|
def complete_payment_session(payment_session:, params: {})
|
|
75
84
|
raise ::NotImplementedError, 'You must implement complete_payment_session method for this gateway.'
|
|
76
85
|
end
|
|
77
86
|
|
|
87
|
+
# Parses an incoming webhook payload from the payment provider.
|
|
88
|
+
# Override in gateway subclasses to implement provider-specific webhook parsing.
|
|
89
|
+
#
|
|
90
|
+
# @param raw_body [String] the raw request body
|
|
91
|
+
# @param headers [Hash] the request headers
|
|
92
|
+
# @return [Hash, nil] normalized result or nil for unsupported events
|
|
93
|
+
# { action: :captured/:authorized/:failed/:canceled,
|
|
94
|
+
# payment_session: <Spree::PaymentSession>,
|
|
95
|
+
# metadata: {} }
|
|
96
|
+
# @raise [Spree::PaymentMethod::WebhookSignatureError] if signature is invalid
|
|
97
|
+
def parse_webhook_event(raw_body, headers)
|
|
98
|
+
raise ::NotImplementedError, 'You must implement parse_webhook_event method for this gateway.'
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns the webhook URL for this payment method.
|
|
102
|
+
# @return [String, nil]
|
|
103
|
+
def webhook_url
|
|
104
|
+
store = stores.first
|
|
105
|
+
return nil unless store
|
|
106
|
+
|
|
107
|
+
"#{store.url_or_custom_domain}/api/v3/webhooks/payments/#{prefixed_id}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class WebhookSignatureError < StandardError; end
|
|
111
|
+
|
|
78
112
|
# Whether this payment method supports setup sessions (saving payment methods for future use).
|
|
79
113
|
# Override in gateway subclasses that support tokenization without a payment.
|
|
80
114
|
def setup_session_supported?
|
|
@@ -76,6 +76,24 @@ module Spree
|
|
|
76
76
|
expires_at.present? && expires_at <= Time.current
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
# Creates or finds the Spree::Payment record for this session.
|
|
80
|
+
# Gateway subclasses can override this in their PaymentSession subclass
|
|
81
|
+
# to handle gateway-specific source creation (credit cards, wallets, etc).
|
|
82
|
+
#
|
|
83
|
+
# @param metadata [Hash] gateway-specific metadata
|
|
84
|
+
# @return [Spree::Payment] the payment record
|
|
85
|
+
def find_or_create_payment!(metadata = {})
|
|
86
|
+
return payment if payment.present?
|
|
87
|
+
|
|
88
|
+
order.payments.find_or_create_by!(
|
|
89
|
+
payment_method: payment_method,
|
|
90
|
+
response_code: external_id,
|
|
91
|
+
) do |p|
|
|
92
|
+
p.amount = amount
|
|
93
|
+
p.skip_source_requirement = true
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
79
97
|
private
|
|
80
98
|
|
|
81
99
|
def publish_processing_event
|
data/app/models/spree/product.rb
CHANGED
|
@@ -114,10 +114,12 @@ module Spree
|
|
|
114
114
|
has_many :orders, through: :line_items
|
|
115
115
|
has_many :completed_orders, -> { reorder(nil).distinct.complete }, through: :line_items, source: :order
|
|
116
116
|
|
|
117
|
+
has_many :media, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'Spree::Asset'
|
|
118
|
+
|
|
117
119
|
has_many :variant_images, -> { order(:position) }, source: :images, through: :variants_including_master
|
|
118
120
|
has_many :variant_images_without_master, -> { order(:position) }, source: :images, through: :variants
|
|
119
121
|
|
|
120
|
-
belongs_to :
|
|
122
|
+
belongs_to :primary_media, class_name: 'Spree::Asset', optional: true, foreign_key: :primary_media_id
|
|
121
123
|
|
|
122
124
|
has_many :option_value_variants, class_name: 'Spree::OptionValueVariant', through: :variants
|
|
123
125
|
has_many :option_values, class_name: 'Spree::OptionValue', through: :variants
|
|
@@ -335,17 +337,26 @@ module Spree
|
|
|
335
337
|
@default_variant_id ||= default_variant.id
|
|
336
338
|
end
|
|
337
339
|
|
|
338
|
-
# Returns
|
|
339
|
-
# Uses
|
|
340
|
+
# Returns the product's media gallery.
|
|
341
|
+
# Uses product-level media if present, otherwise falls back to variant images.
|
|
342
|
+
# @return [ActiveRecord::Relation]
|
|
343
|
+
def gallery_media
|
|
344
|
+
return media if association(:media).loaded? ? media.any? : media.exists?
|
|
345
|
+
|
|
346
|
+
variant_images
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Returns true if the product has any media (product-level or variant-level).
|
|
350
|
+
# Uses counter cache for performance.
|
|
340
351
|
# @return [Boolean]
|
|
341
|
-
def
|
|
352
|
+
def has_media?
|
|
342
353
|
return variant_images.any? if association(:variant_images).loaded?
|
|
343
354
|
|
|
344
|
-
|
|
355
|
+
media_count.positive?
|
|
345
356
|
end
|
|
346
357
|
|
|
347
|
-
|
|
348
|
-
alias
|
|
358
|
+
alias has_images? has_media?
|
|
359
|
+
alias has_variant_images? has_media?
|
|
349
360
|
|
|
350
361
|
# Returns the variant that should be used for displaying images.
|
|
351
362
|
# Priority: master > default_variant > first variant with images
|
|
@@ -354,47 +365,47 @@ module Spree
|
|
|
354
365
|
@variant_for_images ||= find_variant_for_images
|
|
355
366
|
end
|
|
356
367
|
|
|
357
|
-
#
|
|
358
|
-
# Uses cached thumbnail_id which is updated when images are added/removed/reordered.
|
|
359
|
-
# @return [Spree::Image, nil]
|
|
368
|
+
# @deprecated Use #primary_media instead.
|
|
360
369
|
def default_image
|
|
361
|
-
|
|
370
|
+
Spree::Deprecation.warn('Spree::Product#default_image is deprecated and will be removed in Spree 6.0. Please use Spree::Product#primary_media instead.')
|
|
371
|
+
primary_media
|
|
362
372
|
end
|
|
363
373
|
|
|
364
|
-
#
|
|
365
|
-
# @deprecated Use Spree::Product#default_image instead.
|
|
374
|
+
# @deprecated Use #primary_media instead.
|
|
366
375
|
def featured_image
|
|
367
|
-
Spree::Deprecation.warn('Spree::Product#featured_image is deprecated and will be removed in Spree
|
|
376
|
+
Spree::Deprecation.warn('Spree::Product#featured_image is deprecated and will be removed in Spree 6.0. Please use Spree::Product#primary_media instead.')
|
|
377
|
+
primary_media
|
|
378
|
+
end
|
|
368
379
|
|
|
369
|
-
|
|
380
|
+
# @deprecated Use #primary_media instead.
|
|
381
|
+
def primary_image
|
|
382
|
+
Spree::Deprecation.warn('Spree::Product#primary_image is deprecated and will be removed in Spree 6.0. Please use Spree::Product#primary_media instead.')
|
|
383
|
+
primary_media
|
|
370
384
|
end
|
|
371
385
|
|
|
372
|
-
# Returns secondary
|
|
373
|
-
# @return [Spree::
|
|
386
|
+
# Returns secondary media for Product (for hover effects).
|
|
387
|
+
# @return [Spree::Asset, nil]
|
|
374
388
|
def secondary_image
|
|
375
389
|
variant_for_images&.secondary_image
|
|
376
390
|
end
|
|
377
391
|
|
|
378
|
-
#
|
|
379
|
-
alias primary_image default_image
|
|
380
|
-
|
|
381
|
-
# Returns the image count from the variant used for displaying images.
|
|
382
|
-
# @return [Integer]
|
|
392
|
+
# @deprecated Use media_count instead
|
|
383
393
|
def image_count
|
|
384
|
-
|
|
394
|
+
media_count
|
|
385
395
|
end
|
|
386
396
|
|
|
387
|
-
# Updates
|
|
388
|
-
#
|
|
397
|
+
# Updates primary_media_id to the first media item.
|
|
398
|
+
# Checks product-level media first, then falls back to variant images.
|
|
399
|
+
# Called when media is added, removed, or reordered.
|
|
389
400
|
def update_thumbnail!
|
|
390
|
-
|
|
391
|
-
update_column(:
|
|
401
|
+
first_media = media.order(:position).first || variant_images.order(:position).first
|
|
402
|
+
update_column(:primary_media_id, first_media&.id)
|
|
392
403
|
end
|
|
393
404
|
|
|
394
|
-
# Finds first variant with
|
|
405
|
+
# Finds first variant with media using preloaded data when available.
|
|
395
406
|
# @return [Spree::Variant, nil]
|
|
396
407
|
def find_variant_with_images
|
|
397
|
-
return variants.find(&:
|
|
408
|
+
return variants.find(&:has_media?) if variants.loaded?
|
|
398
409
|
|
|
399
410
|
variants.joins(:images).first
|
|
400
411
|
end
|
|
@@ -630,12 +641,12 @@ module Spree
|
|
|
630
641
|
|
|
631
642
|
private
|
|
632
643
|
|
|
633
|
-
# Determines which variant should be used for displaying
|
|
634
|
-
# Priority: master > default_variant > first variant with
|
|
644
|
+
# Determines which variant should be used for displaying media.
|
|
645
|
+
# Priority: master > default_variant > first variant with media
|
|
635
646
|
def find_variant_for_images
|
|
636
|
-
return master if master.
|
|
637
|
-
return default_variant if has_variants? && default_variant.
|
|
638
|
-
return find_variant_with_images if
|
|
647
|
+
return master if master.has_media?
|
|
648
|
+
return default_variant if has_variants? && default_variant.has_media?
|
|
649
|
+
return find_variant_with_images if has_media?
|
|
639
650
|
|
|
640
651
|
nil
|
|
641
652
|
end
|
data/app/models/spree/store.rb
CHANGED
|
@@ -58,6 +58,7 @@ module Spree
|
|
|
58
58
|
#
|
|
59
59
|
# Associations
|
|
60
60
|
#
|
|
61
|
+
has_many :carts, -> { incomplete }, class_name: 'Spree::Order', inverse_of: :store
|
|
61
62
|
has_many :checkouts, -> { incomplete }, class_name: 'Spree::Order', inverse_of: :store
|
|
62
63
|
has_many :orders, class_name: 'Spree::Order'
|
|
63
64
|
has_many :line_items, through: :orders, class_name: 'Spree::LineItem'
|
|
@@ -108,6 +109,7 @@ module Spree
|
|
|
108
109
|
has_many :customer_groups, class_name: 'Spree::CustomerGroup', dependent: :destroy, inverse_of: :store
|
|
109
110
|
|
|
110
111
|
has_many :api_keys, class_name: 'Spree::ApiKey', dependent: :destroy
|
|
112
|
+
has_many :allowed_origins, class_name: 'Spree::AllowedOrigin', dependent: :destroy
|
|
111
113
|
|
|
112
114
|
#
|
|
113
115
|
# Validations
|
|
@@ -251,6 +253,36 @@ module Spree
|
|
|
251
253
|
formatted_url
|
|
252
254
|
end
|
|
253
255
|
|
|
256
|
+
# Returns the storefront origin URL for use in customer-facing emails and links.
|
|
257
|
+
# Uses the first allowed origin if configured, otherwise falls back to formatted_url.
|
|
258
|
+
#
|
|
259
|
+
# @return [String] e.g. "https://myshop.com"
|
|
260
|
+
def storefront_url
|
|
261
|
+
allowed_origins.order(:created_at).pick(:origin) || formatted_url
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Returns true if the given URL's origin matches one of the store's allowed origins.
|
|
265
|
+
# Comparison is port-less: only scheme + host are matched, so storing
|
|
266
|
+
# `http://localhost` will match `http://localhost:3000`, `http://localhost:4000`, etc.
|
|
267
|
+
#
|
|
268
|
+
# @param url [String] the full URL to check
|
|
269
|
+
# @return [Boolean]
|
|
270
|
+
def allowed_origin?(url)
|
|
271
|
+
return false if url.blank?
|
|
272
|
+
|
|
273
|
+
uri = URI.parse(url)
|
|
274
|
+
request_origin = "#{uri.scheme}://#{uri.host}"
|
|
275
|
+
|
|
276
|
+
allowed_origins.pluck(:origin).any? do |stored|
|
|
277
|
+
stored_uri = URI.parse(stored)
|
|
278
|
+
"#{stored_uri.scheme}://#{stored_uri.host}" == request_origin
|
|
279
|
+
rescue URI::InvalidURIError
|
|
280
|
+
false
|
|
281
|
+
end
|
|
282
|
+
rescue URI::InvalidURIError
|
|
283
|
+
false
|
|
284
|
+
end
|
|
285
|
+
|
|
254
286
|
# Returns the states available for checkout for the store
|
|
255
287
|
# @param country [Spree::Country] the country to get the states for
|
|
256
288
|
# @return [Array<Spree::State>]
|
data/app/models/spree/variant.rb
CHANGED
|
@@ -48,8 +48,8 @@ module Spree
|
|
|
48
48
|
has_many :option_value_variants, class_name: 'Spree::OptionValueVariant'
|
|
49
49
|
has_many :option_values, through: :option_value_variants, dependent: :destroy, class_name: 'Spree::OptionValue'
|
|
50
50
|
|
|
51
|
-
has_many :images, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'Spree::
|
|
52
|
-
belongs_to :
|
|
51
|
+
has_many :images, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: 'Spree::Asset'
|
|
52
|
+
belongs_to :primary_media, class_name: 'Spree::Asset', optional: true, foreign_key: :primary_media_id
|
|
53
53
|
|
|
54
54
|
has_many :prices,
|
|
55
55
|
class_name: 'Spree::Price',
|
|
@@ -279,26 +279,35 @@ module Spree
|
|
|
279
279
|
is_master? ? name + ' - Master' : name + ' - ' + options_text
|
|
280
280
|
end
|
|
281
281
|
|
|
282
|
-
# Returns
|
|
282
|
+
# Returns the variant's media gallery.
|
|
283
|
+
# Currently returns direct images. In 6.0 will use variant_media join table.
|
|
284
|
+
# @return [ActiveRecord::Relation]
|
|
285
|
+
def gallery_media
|
|
286
|
+
images
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Returns true if the variant has media.
|
|
283
290
|
# Uses loaded association when available, otherwise falls back to counter cache.
|
|
284
291
|
# @return [Boolean]
|
|
285
|
-
def
|
|
292
|
+
def has_media?
|
|
286
293
|
return images.any? if images.loaded?
|
|
287
294
|
|
|
288
|
-
|
|
295
|
+
media_count.positive?
|
|
289
296
|
end
|
|
290
297
|
|
|
291
|
-
|
|
292
|
-
|
|
298
|
+
alias has_images? has_media?
|
|
299
|
+
|
|
300
|
+
# @deprecated Use #primary_media instead.
|
|
293
301
|
def default_image
|
|
294
|
-
|
|
302
|
+
Spree::Deprecation.warn('Spree::Variant#default_image is deprecated and will be removed in Spree 6.0. Please use Spree::Variant#primary_media instead.')
|
|
303
|
+
primary_media
|
|
295
304
|
end
|
|
296
305
|
|
|
297
|
-
# Updates
|
|
298
|
-
# Called when
|
|
306
|
+
# Updates primary_media_id to the first media item by position.
|
|
307
|
+
# Called when media is added, removed, or reordered.
|
|
299
308
|
def update_thumbnail!
|
|
300
|
-
|
|
301
|
-
update_column(:
|
|
309
|
+
first_media = images.order(:position).first
|
|
310
|
+
update_column(:primary_media_id, first_media&.id)
|
|
302
311
|
end
|
|
303
312
|
|
|
304
313
|
# Returns first Image for Variant.
|
|
@@ -3,49 +3,22 @@ module Spree
|
|
|
3
3
|
class Create
|
|
4
4
|
prepend Spree::ServiceModule::Base
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
# @param store [Spree::Store] the store for the cart
|
|
8
|
-
# @param currency [String, nil] ISO currency code, defaults to store's default currency
|
|
9
|
-
# @param locale [String, nil] locale for the cart (e.g. 'en', 'fr'), defaults to Spree::Current.locale
|
|
10
|
-
# @param public_metadata [Hash] public metadata for the order
|
|
11
|
-
# @param private_metadata [Hash] private metadata for the order
|
|
12
|
-
# @param order_params [Hash] additional order attributes
|
|
13
|
-
# @param line_items [Array<Hash>] line items to add, each with :variant_id (prefixed) and :quantity
|
|
14
|
-
# @return [Spree::ServiceModule::Result]
|
|
15
|
-
def call(user:, store:, currency:, locale: nil, metadata: {}, public_metadata: {}, private_metadata: {}, order_params: {}, line_items: [])
|
|
6
|
+
def call(user:, store:, currency:, public_metadata: {}, private_metadata: {}, order_params: {})
|
|
16
7
|
order_params ||= {}
|
|
17
|
-
line_items ||= []
|
|
18
8
|
|
|
19
9
|
# we cannot create an order without store
|
|
20
10
|
return failure(:store_is_required) if store.nil?
|
|
21
11
|
|
|
22
|
-
resolved_metadata = metadata.presence || private_metadata
|
|
23
|
-
|
|
24
12
|
default_params = {
|
|
25
13
|
user: user,
|
|
26
14
|
currency: currency || store.default_currency,
|
|
27
|
-
locale: locale || Spree::Current.locale,
|
|
28
15
|
token: Spree::GenerateToken.new.call(Spree::Order),
|
|
29
16
|
public_metadata: public_metadata.to_h,
|
|
30
|
-
private_metadata:
|
|
17
|
+
private_metadata: private_metadata.to_h
|
|
31
18
|
}
|
|
32
19
|
|
|
33
|
-
order =
|
|
34
|
-
|
|
35
|
-
ApplicationRecord.transaction do
|
|
36
|
-
order = store.orders.create!(default_params.merge(order_params))
|
|
37
|
-
|
|
38
|
-
if line_items.present?
|
|
39
|
-
result = Spree.cart_upsert_items_service.call(order: order, line_items: line_items)
|
|
40
|
-
raise StandardError, result.error.to_s if result.failure?
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
20
|
+
order = store.orders.create!(default_params.merge(order_params))
|
|
44
21
|
success(order)
|
|
45
|
-
rescue ActiveRecord::RecordNotFound
|
|
46
|
-
raise
|
|
47
|
-
rescue StandardError => e
|
|
48
|
-
failure(order, e.message)
|
|
49
22
|
end
|
|
50
23
|
end
|
|
51
24
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# In Spree 6 this servoice will complete the Spree::Cart, and create a Spree::Order
|
|
2
|
+
# created based on the contents of the cart.
|
|
3
|
+
module Spree
|
|
4
|
+
module Carts
|
|
5
|
+
class Complete
|
|
6
|
+
prepend Spree::ServiceModule::Base
|
|
7
|
+
|
|
8
|
+
# Completes the cart and creates a Spree::Order based on its contents.
|
|
9
|
+
# @return [Spree::Order]
|
|
10
|
+
def call(cart:)
|
|
11
|
+
return success(cart) if cart.completed?
|
|
12
|
+
return failure(cart, 'Order is canceled') if cart.canceled?
|
|
13
|
+
|
|
14
|
+
cart.with_lock do
|
|
15
|
+
process_payments!(cart) if cart.payment_required?
|
|
16
|
+
|
|
17
|
+
return failure(cart, cart.errors.full_messages.to_sentence) if cart.errors.any?
|
|
18
|
+
|
|
19
|
+
advance_to_complete!(cart)
|
|
20
|
+
|
|
21
|
+
if cart.reload.complete?
|
|
22
|
+
success(cart)
|
|
23
|
+
else
|
|
24
|
+
failure(cart, cart.errors.full_messages.to_sentence.presence || 'Could not complete checkout')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def process_payments!(cart)
|
|
32
|
+
# If payments were already processed by the payment session
|
|
33
|
+
# (e.g. Stripe charged the card during complete_payment_session),
|
|
34
|
+
# skip re-processing. Only process unprocessed (checkout state) payments.
|
|
35
|
+
return if cart.payment_total >= cart.total
|
|
36
|
+
return if cart.payments.valid.any?(&:completed?) && cart.unprocessed_payments.empty?
|
|
37
|
+
|
|
38
|
+
cart.process_payments!
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def advance_to_complete!(cart)
|
|
42
|
+
cart.next until cart.complete? || cart.errors.present?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Carts
|
|
3
|
+
class Create
|
|
4
|
+
prepend Spree::ServiceModule::Base
|
|
5
|
+
|
|
6
|
+
def call(params: {})
|
|
7
|
+
@params = params.to_h.deep_symbolize_keys
|
|
8
|
+
|
|
9
|
+
store = @params.delete(:store)
|
|
10
|
+
return failure(:store_is_required) if store.nil?
|
|
11
|
+
|
|
12
|
+
cart = store.carts.create!(
|
|
13
|
+
user: @params.delete(:user),
|
|
14
|
+
currency: @params.delete(:currency) || store.default_currency,
|
|
15
|
+
locale: @params.delete(:locale) || Spree::Current.locale
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Delegate all attribute/address/item processing to Carts::Update
|
|
19
|
+
if @params.present?
|
|
20
|
+
result = Spree::Carts::Update.call(cart: cart, params: @params)
|
|
21
|
+
return result if result.failure?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
success(cart.reload)
|
|
25
|
+
rescue ActiveRecord::RecordNotFound
|
|
26
|
+
raise
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
failure(nil, e.message)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|