spree_core 5.3.6 → 5.4.0.beta
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/finders/spree/orders/find_complete.rb +33 -2
- data/app/finders/spree/stores/find_default.rb +17 -0
- data/app/finders/spree/variants/visible_finder.rb +1 -0
- data/app/helpers/spree/addresses_helper.rb +1 -2
- data/app/helpers/spree/base_helper.rb +5 -4
- data/app/jobs/spree/api_key_touch_job.rb +9 -0
- data/app/jobs/spree/images/save_from_url_job.rb +23 -47
- data/app/models/concerns/spree/admin_user_methods.rb +32 -0
- data/app/models/concerns/spree/number_as_param.rb +5 -3
- data/app/models/concerns/spree/prefixed_id.rb +82 -0
- data/app/models/concerns/spree/product_scopes.rb +33 -18
- data/app/models/concerns/spree/publishable.rb +47 -47
- data/app/models/concerns/spree/{multi_store_resource.rb → store_scoped_resource.rb} +1 -10
- data/app/models/concerns/spree/stores/markets.rb +124 -0
- data/app/models/concerns/spree/user_methods.rb +7 -7
- data/app/models/concerns/spree/user_payment_source.rb +2 -0
- data/app/models/concerns/spree/user_roles.rb +1 -0
- data/app/models/spree/ability.rb +13 -0
- data/app/models/spree/address.rb +30 -0
- data/app/models/spree/adjustment.rb +2 -0
- data/app/models/spree/api_key.rb +47 -0
- data/app/models/spree/asset.rb +2 -0
- data/app/models/spree/authentication/strategies/base_strategy.rb +55 -0
- data/app/models/spree/authentication/strategies/email_password_strategy.rb +47 -0
- data/app/models/spree/base.rb +1 -0
- data/app/models/spree/calculator.rb +2 -0
- data/app/models/spree/country.rb +56 -0
- data/app/models/spree/coupon_code.rb +2 -0
- data/app/models/spree/credit_card.rb +2 -0
- data/app/models/spree/current.rb +9 -4
- data/app/models/spree/customer_group.rb +2 -0
- data/app/models/spree/customer_return.rb +2 -0
- data/app/models/spree/data_feed.rb +2 -0
- data/app/models/spree/digital.rb +2 -0
- data/app/models/spree/digital_link.rb +2 -0
- data/app/models/spree/export.rb +5 -4
- data/app/models/spree/fulfilment_changer.rb +8 -2
- data/app/models/spree/gateway/bogus.rb +60 -0
- data/app/models/spree/gateway_customer.rb +2 -0
- data/app/models/spree/gift_card.rb +2 -0
- data/app/models/spree/gift_card_batch.rb +2 -4
- data/app/models/spree/image.rb +18 -0
- data/app/models/spree/import.rb +5 -3
- data/app/models/spree/import_mapping.rb +2 -0
- data/app/models/spree/import_row.rb +2 -0
- data/app/models/spree/import_schemas/customers.rb +21 -0
- data/app/models/spree/imports/customers.rb +9 -0
- data/app/models/spree/integration.rb +2 -0
- data/app/models/spree/inventory_unit.rb +2 -0
- data/app/models/spree/invitation.rb +6 -6
- data/app/models/spree/legacy_admin_user.rb +31 -0
- data/app/models/spree/legacy_user.rb +19 -1
- data/app/models/spree/line_item.rb +15 -4
- data/app/models/spree/log_entry.rb +2 -0
- data/app/models/spree/market.rb +83 -0
- data/app/models/spree/market_country.rb +25 -0
- data/app/models/spree/metafield.rb +2 -0
- data/app/models/spree/metafield_definition.rb +2 -0
- data/app/models/spree/newsletter_subscriber.rb +2 -0
- data/app/models/spree/option_type.rb +2 -0
- data/app/models/spree/option_value.rb +2 -0
- data/app/models/spree/order/address_book.rb +2 -1
- data/app/models/spree/order.rb +75 -47
- data/app/models/spree/payment.rb +6 -1
- data/app/models/spree/payment_capture_event.rb +2 -0
- data/app/models/spree/payment_method.rb +59 -1
- data/app/models/spree/payment_session.rb +109 -0
- data/app/models/spree/payment_sessions/bogus.rb +4 -0
- data/app/models/spree/payment_setup_session.rb +79 -0
- data/app/models/spree/payment_source.rb +2 -0
- data/app/models/spree/permission_sets/default_customer.rb +19 -0
- data/app/models/spree/policy.rb +2 -0
- data/app/models/spree/post.rb +2 -0
- data/app/models/spree/post_category.rb +2 -0
- data/app/models/spree/price.rb +26 -2
- data/app/models/spree/price_list.rb +2 -0
- data/app/models/spree/price_rule.rb +2 -0
- data/app/models/spree/price_rules/market_rule.rb +19 -0
- data/app/models/spree/product.rb +41 -29
- data/app/models/spree/promotion/rules/country.rb +1 -1
- data/app/models/spree/promotion.rb +3 -1
- data/app/models/spree/promotion_action.rb +2 -0
- data/app/models/spree/promotion_category.rb +2 -0
- data/app/models/spree/promotion_rule.rb +2 -0
- data/app/models/spree/prototype.rb +2 -0
- data/app/models/spree/refund.rb +2 -4
- data/app/models/spree/refund_reason.rb +2 -0
- data/app/models/spree/reimbursement/credit.rb +2 -0
- data/app/models/spree/reimbursement.rb +2 -0
- data/app/models/spree/reimbursement_type.rb +2 -0
- data/app/models/spree/report.rb +3 -1
- data/app/models/spree/return_authorization.rb +2 -0
- data/app/models/spree/return_authorization_reason.rb +2 -0
- data/app/models/spree/return_item.rb +2 -0
- data/app/models/spree/role.rb +2 -0
- data/app/models/spree/shipment.rb +11 -1
- data/app/models/spree/shipping_category.rb +2 -0
- data/app/models/spree/shipping_method.rb +2 -0
- data/app/models/spree/shipping_method_category.rb +2 -0
- data/app/models/spree/shipping_rate.rb +2 -0
- data/app/models/spree/state_change.rb +2 -0
- data/app/models/spree/stock_item.rb +2 -0
- data/app/models/spree/stock_location.rb +2 -0
- data/app/models/spree/stock_movement.rb +2 -0
- data/app/models/spree/stock_transfer.rb +2 -1
- data/app/models/spree/store.rb +110 -179
- data/app/models/spree/store_credit.rb +2 -4
- data/app/models/spree/store_credit_category.rb +2 -0
- data/app/models/spree/store_credit_event.rb +2 -0
- data/app/models/spree/store_credit_type.rb +2 -0
- data/app/models/spree/store_product.rb +2 -0
- data/app/models/spree/tax_category.rb +2 -0
- data/app/models/spree/tax_rate.rb +2 -0
- data/app/models/spree/taxon.rb +4 -1
- data/app/models/spree/taxon_image.rb +8 -0
- data/app/models/spree/taxon_rule.rb +2 -0
- data/app/models/spree/taxonomy.rb +2 -0
- data/app/models/spree/user_identity.rb +81 -0
- data/app/models/spree/variant.rb +13 -3
- data/app/models/spree/webhook_delivery.rb +2 -0
- data/app/models/spree/webhook_endpoint.rb +2 -17
- data/app/models/spree/wished_item.rb +15 -0
- data/app/models/spree/wishlist.rb +2 -8
- data/app/models/spree/zone.rb +2 -6
- data/app/presenters/spree/filters/price_presenter.rb +1 -0
- data/app/presenters/spree/filters/price_range_presenter.rb +1 -0
- data/app/presenters/spree/filters/quantified_price_range_presenter.rb +1 -0
- data/app/presenters/spree/product_summary_presenter.rb +1 -0
- data/app/services/spree/addresses/helper.rb +22 -3
- data/app/services/spree/cart/associate.rb +19 -6
- data/app/services/spree/checkout/select_shipping_method.rb +13 -1
- data/app/services/spree/classifications/reposition.rb +5 -0
- data/app/services/spree/data_feeds/google/required_attributes.rb +8 -3
- data/app/services/spree/gift_cards/apply.rb +4 -5
- data/app/services/spree/imports/row_processors/customer.rb +70 -0
- data/app/services/spree/orders/approve.rb +5 -3
- data/app/services/spree/orders/cancel.rb +9 -4
- data/app/services/spree/products/prepare_nested_attributes.rb +1 -1
- data/app/services/spree/sample_data/import_runner.rb +54 -0
- data/app/services/spree/sample_data/loader.rb +78 -0
- data/app/services/spree/seeds/admin_user.rb +2 -3
- data/app/services/spree/seeds/all.rb +1 -0
- data/app/services/spree/seeds/api_keys.rb +16 -0
- data/app/services/spree/seeds/stores.rb +2 -4
- data/app/sorters/spree/orders/sort.rb +4 -0
- data/app/subscribers/spree/product_metrics_subscriber.rb +4 -4
- data/config/brakeman.ignore +120 -0
- data/config/locales/en.yml +20 -5
- data/db/migrate/20250923141900_create_spree_user_identities.rb +17 -0
- data/db/migrate/20260123000000_create_spree_api_keys.rb +19 -0
- data/db/migrate/20260131000000_add_thumbnail_id_to_spree_variants_and_products.rb +9 -0
- data/db/migrate/20260213000000_create_spree_payment_sessions.rb +27 -0
- data/db/migrate/20260218000000_create_spree_payment_setup_sessions.rb +24 -0
- data/db/migrate/20260220000000_create_spree_markets.rb +29 -0
- data/db/sample_data/customers.csv +21 -0
- data/db/sample_data/metafield_definitions.rb +7 -0
- data/db/sample_data/orders.rb +131 -0
- data/db/sample_data/payment_methods.rb +17 -0
- data/db/sample_data/posts.rb +7 -0
- data/db/sample_data/products.csv +1083 -0
- data/db/sample_data/promotions.rb +8 -0
- data/db/sample_data/shipping_methods.rb +39 -0
- data/lib/generators/spree/authentication/devise/devise_generator.rb +2 -2
- data/lib/generators/spree/authentication/dummy/dummy_generator.rb +54 -0
- data/lib/generators/spree/authentication/dummy/templates/authentication_helpers.rb.tt +52 -0
- data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +33 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
- data/lib/spree/core/configuration.rb +1 -1
- data/lib/spree/core/controller_helpers/common.rb +6 -0
- data/lib/spree/core/controller_helpers/currency.rb +5 -0
- data/lib/spree/core/controller_helpers/order.rb +5 -1
- data/lib/spree/core/controller_helpers/store.rb +1 -1
- data/lib/spree/core/dependencies.rb +1 -1
- data/lib/spree/core/engine.rb +17 -4
- data/lib/spree/core/pricing/context.rb +6 -3
- data/lib/spree/core/product_filters.rb +14 -0
- data/lib/spree/core/query_filters/comparable.rb +4 -0
- data/lib/spree/core/query_filters/text.rb +4 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +4 -4
- data/lib/spree/database_type_utilities.rb +7 -0
- data/lib/spree/events.rb +17 -10
- data/lib/spree/money.rb +2 -9
- data/lib/spree/permitted_attributes.rb +19 -7
- data/lib/spree/testing_support/capybara_config.rb +2 -2
- data/lib/spree/testing_support/common_rake.rb +15 -4
- data/lib/spree/testing_support/factories/api_key_factory.rb +19 -0
- data/lib/spree/testing_support/factories/custom_domain_factory.rb +7 -5
- data/lib/spree/testing_support/factories/import_factory.rb +12 -0
- data/lib/spree/testing_support/factories/market_factory.rb +35 -0
- data/lib/spree/testing_support/factories/order_factory.rb +3 -1
- data/lib/spree/testing_support/factories/payment_method_factory.rb +2 -0
- data/lib/spree/testing_support/factories/payment_session_factory.rb +47 -0
- data/lib/spree/testing_support/factories/payment_setup_session_factory.rb +31 -0
- data/lib/spree/testing_support/factories/price_rule_factory.rb +10 -0
- data/lib/spree/testing_support/factories/user_identity_factory.rb +15 -0
- data/lib/spree/testing_support/store.rb +3 -2
- data/lib/spree/webhooks.rb +7 -7
- data/lib/tasks/images.rake +20 -0
- data/lib/tasks/markets.rake +40 -0
- data/lib/tasks/sample_data.rake +15 -0
- data/spec/fixtures/files/customers_import.csv +4 -0
- metadata +88 -63
- data/LICENSE.md +0 -57
- data/app/finders/spree/stores/find_current.rb +0 -28
- data/app/models/spree/custom_domain.rb +0 -59
- data/app/presenters/spree/csv/formula_sanitizer.rb +0 -28
- data/app/serializers/spree/events/asset_serializer.rb +0 -22
- data/app/serializers/spree/events/base_serializer.rb +0 -61
- data/app/serializers/spree/events/customer_return_serializer.rb +0 -20
- data/app/serializers/spree/events/digital_link_serializer.rb +0 -20
- data/app/serializers/spree/events/digital_serializer.rb +0 -18
- data/app/serializers/spree/events/export_serializer.rb +0 -22
- data/app/serializers/spree/events/gift_card_batch_serializer.rb +0 -24
- data/app/serializers/spree/events/gift_card_serializer.rb +0 -29
- data/app/serializers/spree/events/image_serializer.rb +0 -9
- data/app/serializers/spree/events/import_row_serializer.rb +0 -23
- data/app/serializers/spree/events/import_serializer.rb +0 -24
- data/app/serializers/spree/events/invitation_serializer.rb +0 -28
- data/app/serializers/spree/events/line_item_serializer.rb +0 -31
- data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +0 -21
- data/app/serializers/spree/events/order_serializer.rb +0 -39
- data/app/serializers/spree/events/payment_serializer.rb +0 -24
- data/app/serializers/spree/events/post_category_serializer.rb +0 -20
- data/app/serializers/spree/events/post_serializer.rb +0 -26
- data/app/serializers/spree/events/price_serializer.rb +0 -22
- data/app/serializers/spree/events/product_serializer.rb +0 -24
- data/app/serializers/spree/events/promotion_serializer.rb +0 -32
- data/app/serializers/spree/events/refund_serializer.rb +0 -23
- data/app/serializers/spree/events/reimbursement_serializer.rb +0 -22
- data/app/serializers/spree/events/report_serializer.rb +0 -23
- data/app/serializers/spree/events/return_authorization_serializer.rb +0 -22
- data/app/serializers/spree/events/return_item_serializer.rb +0 -27
- data/app/serializers/spree/events/shipment_serializer.rb +0 -24
- data/app/serializers/spree/events/stock_item_serializer.rb +0 -22
- data/app/serializers/spree/events/stock_movement_serializer.rb +0 -22
- data/app/serializers/spree/events/stock_transfer_serializer.rb +0 -22
- data/app/serializers/spree/events/store_credit_serializer.rb +0 -30
- data/app/serializers/spree/events/user_serializer.rb +0 -18
- data/app/serializers/spree/events/variant_serializer.rb +0 -34
- data/app/serializers/spree/events/wished_item_serializer.rb +0 -20
- data/app/serializers/spree/events/wishlist_serializer.rb +0 -22
data/app/models/spree/order.rb
CHANGED
|
@@ -8,6 +8,8 @@ require_dependency 'spree/order/gift_card'
|
|
|
8
8
|
|
|
9
9
|
module Spree
|
|
10
10
|
class Order < Spree.base_class
|
|
11
|
+
has_prefix_id :or # Stripe: or_
|
|
12
|
+
|
|
11
13
|
PAYMENT_STATES = %w(balance_due credit_owed failed paid void)
|
|
12
14
|
SHIPMENT_STATES = %w(backorder canceled partial pending ready shipped)
|
|
13
15
|
LINE_ITEM_REMOVABLE_STATES = %w(cart address delivery payment confirm resumed)
|
|
@@ -26,7 +28,6 @@ module Spree
|
|
|
26
28
|
include Spree::Order::GiftCard
|
|
27
29
|
|
|
28
30
|
include Spree::NumberIdentifier
|
|
29
|
-
include Spree::NumberAsParam
|
|
30
31
|
include Spree::SingleStoreResource
|
|
31
32
|
|
|
32
33
|
publishes_lifecycle_events
|
|
@@ -98,7 +99,7 @@ module Spree
|
|
|
98
99
|
acts_as_taggable_on :tags
|
|
99
100
|
acts_as_taggable_tenant :store_id
|
|
100
101
|
|
|
101
|
-
ASSOCIATED_USER_ATTRIBUTES = [:user_id, :email, :
|
|
102
|
+
ASSOCIATED_USER_ATTRIBUTES = [:user_id, :email, :bill_address_id, :ship_address_id]
|
|
102
103
|
|
|
103
104
|
belongs_to :user, class_name: "::#{Spree.user_class}", optional: true, autosave: true
|
|
104
105
|
belongs_to :created_by, class_name: "::#{Spree.admin_user_class}", optional: true
|
|
@@ -121,6 +122,7 @@ module Spree
|
|
|
121
122
|
has_many :state_changes, as: :stateful, class_name: 'Spree::StateChange'
|
|
122
123
|
has_many :line_items, -> { order(:created_at) }, inverse_of: :order, class_name: 'Spree::LineItem'
|
|
123
124
|
has_many :payments, class_name: 'Spree::Payment'
|
|
125
|
+
has_many :payment_sessions, inverse_of: :order, class_name: 'Spree::PaymentSession'
|
|
124
126
|
has_many :return_authorizations, inverse_of: :order, class_name: 'Spree::ReturnAuthorization'
|
|
125
127
|
has_many :adjustments, -> { order(:created_at) }, as: :adjustable, class_name: 'Spree::Adjustment'
|
|
126
128
|
end
|
|
@@ -183,6 +185,7 @@ module Spree
|
|
|
183
185
|
validates :shipment_total, MONEY_VALIDATION
|
|
184
186
|
validates :promo_total, NEGATIVE_MONEY_VALIDATION
|
|
185
187
|
validates :total, MONEY_VALIDATION
|
|
188
|
+
validate :currency_must_be_supported_by_store
|
|
186
189
|
|
|
187
190
|
delegate :update_totals, :persist_totals, to: :updater
|
|
188
191
|
delegate :merge!, to: :merger
|
|
@@ -235,6 +238,36 @@ module Spree
|
|
|
235
238
|
left_joins(:bill_address).where(arel_table[:email].lower.eq(query.downcase)).or(where(conditions.reduce(:or)))
|
|
236
239
|
end
|
|
237
240
|
|
|
241
|
+
# Find an order by prefixed ID first, falling back to number, then integer id for backwards compatibility
|
|
242
|
+
# @param param [String] the prefixed ID, number, or integer id to search for
|
|
243
|
+
# @return [Spree::Order, nil] the found order or nil
|
|
244
|
+
def self.find_by_param(param)
|
|
245
|
+
return nil if param.blank?
|
|
246
|
+
|
|
247
|
+
# Try prefixed ID first (new format)
|
|
248
|
+
if param.to_s.include?('_')
|
|
249
|
+
decoded = decode_prefixed_id(param)
|
|
250
|
+
order = find_by(id: decoded) if decoded
|
|
251
|
+
return order if order
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Try number (legacy format)
|
|
255
|
+
order = find_by(number: param)
|
|
256
|
+
return order if order
|
|
257
|
+
|
|
258
|
+
# Fall back to id (numeric legacy format) - only if param looks like an integer
|
|
259
|
+
find_by(id: param) if param.to_s.match?(/\A\d+\z/)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Find an order by prefixed ID first, falling back to number, then integer id for backwards compatibility
|
|
263
|
+
# Raises ActiveRecord::RecordNotFound if not found
|
|
264
|
+
# @param param [String] the prefixed ID, number, or integer id to search for
|
|
265
|
+
# @return [Spree::Order] the found order
|
|
266
|
+
# @raise [ActiveRecord::RecordNotFound] if order not found
|
|
267
|
+
def self.find_by_param!(param)
|
|
268
|
+
find_by_param(param) || raise(ActiveRecord::RecordNotFound.new("Couldn't find Order with param=#{param}"))
|
|
269
|
+
end
|
|
270
|
+
|
|
238
271
|
# Use this method in other gems that wish to register their own custom logic
|
|
239
272
|
# that should be called after Order#update
|
|
240
273
|
def self.register_update_hook(hook)
|
|
@@ -275,9 +308,9 @@ module Spree
|
|
|
275
308
|
# @return [Boolean]
|
|
276
309
|
def order_refunded?
|
|
277
310
|
return false if item_count.zero?
|
|
278
|
-
return false if refunds_total.zero?
|
|
279
311
|
|
|
280
|
-
payment_state.in?(%w[void failed])
|
|
312
|
+
(payment_state.in?(%w[void failed]) && refunds_total.positive?) ||
|
|
313
|
+
refunds_total == total_minus_store_credits - additional_tax_total.abs
|
|
281
314
|
end
|
|
282
315
|
|
|
283
316
|
def refunds_total
|
|
@@ -389,25 +422,13 @@ module Spree
|
|
|
389
422
|
end
|
|
390
423
|
|
|
391
424
|
# Associates the specified user with the order.
|
|
425
|
+
# Delegates to {Spree::Cart::Associate} service.
|
|
426
|
+
#
|
|
427
|
+
# @param user [Spree.user_class] the user to associate with the order
|
|
428
|
+
# @param override_email [Boolean] whether to override the order email with the user's email
|
|
429
|
+
# @return [Spree::ServiceModule::Result]
|
|
392
430
|
def associate_user!(user, override_email = true)
|
|
393
|
-
self
|
|
394
|
-
self.email = user.email if override_email
|
|
395
|
-
# we need to check if user is of admin user class to avoid mismatch type error
|
|
396
|
-
# in a scenario where we have separate classes for admin and regular users
|
|
397
|
-
self.created_by ||= user if user.is_a?(Spree.admin_user_class)
|
|
398
|
-
self.bill_address ||= user.bill_address
|
|
399
|
-
self.ship_address ||= user.ship_address
|
|
400
|
-
|
|
401
|
-
changes = slice(*ASSOCIATED_USER_ATTRIBUTES)
|
|
402
|
-
|
|
403
|
-
# immediately persist the changes we just made, but don't use save
|
|
404
|
-
# since we might have an invalid address associated
|
|
405
|
-
ActiveRecord::Base.connected_to(role: :writing) do
|
|
406
|
-
self.class.unscoped.where(id: self).update_all(changes)
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
# Manually publish update event since update_all bypasses callbacks
|
|
410
|
-
publish_event('order.updated') if changes.present?
|
|
431
|
+
Spree.cart_associate_service.call(guest_order: self, user: user, override_email: override_email)
|
|
411
432
|
end
|
|
412
433
|
|
|
413
434
|
def disassociate_user!
|
|
@@ -555,6 +576,10 @@ module Spree
|
|
|
555
576
|
payments.valid.completed.size == payments.valid.size && payments.valid.sum(:amount) >= total
|
|
556
577
|
end
|
|
557
578
|
|
|
579
|
+
def payment_methods
|
|
580
|
+
@payment_methods ||= store.payment_methods.active.available_on_front_end.select { |pm| pm.available_for_order?(self) }
|
|
581
|
+
end
|
|
582
|
+
|
|
558
583
|
def available_payment_methods(store = nil)
|
|
559
584
|
Spree::Deprecation.warn('`Order#available_payment_methods` is deprecated and will be removed in Spree 5.5. Use `collect_frontend_payment_methods` instead.')
|
|
560
585
|
|
|
@@ -752,30 +777,23 @@ module Spree
|
|
|
752
777
|
!payments.risky.empty?
|
|
753
778
|
end
|
|
754
779
|
|
|
780
|
+
# Cancels the order and records the canceler.
|
|
781
|
+
# Delegates to {Spree::Orders::Cancel} service.
|
|
782
|
+
#
|
|
783
|
+
# @param user [Spree.user_class, nil] the user who canceled the order
|
|
784
|
+
# @param canceled_at [Time, nil] the time of cancellation (defaults to current time)
|
|
785
|
+
# @return [Spree::ServiceModule::Result]
|
|
755
786
|
def canceled_by(user, canceled_at = nil)
|
|
756
|
-
canceled_at
|
|
757
|
-
changes = { canceler_id: user.id, canceled_at: canceled_at }
|
|
758
|
-
|
|
759
|
-
transaction do
|
|
760
|
-
update_columns(changes)
|
|
761
|
-
cancel!
|
|
762
|
-
end
|
|
763
|
-
|
|
764
|
-
# Manually publish update event since update_columns bypasses callbacks
|
|
765
|
-
publish_event('order.canceled')
|
|
787
|
+
Spree.order_cancel_service.call(order: self, canceler: user, canceled_at: canceled_at)
|
|
766
788
|
end
|
|
767
789
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
end
|
|
776
|
-
|
|
777
|
-
# Manually publish update event since update_columns bypasses callbacks
|
|
778
|
-
publish_event('order.approved')
|
|
790
|
+
# Approves the order and records the approver.
|
|
791
|
+
# Delegates to {Spree::Orders::Approve} service.
|
|
792
|
+
#
|
|
793
|
+
# @param user [Spree.user_class, nil] the user who approved the order
|
|
794
|
+
# @return [Spree::ServiceModule::Result]
|
|
795
|
+
def approved_by(user = nil)
|
|
796
|
+
Spree.order_approve_service.call(order: self, approver: user)
|
|
779
797
|
end
|
|
780
798
|
|
|
781
799
|
def approved?
|
|
@@ -806,11 +824,12 @@ module Spree
|
|
|
806
824
|
publish_event('order.updated')
|
|
807
825
|
end
|
|
808
826
|
|
|
827
|
+
# Approves the order without recording an approver.
|
|
828
|
+
# Delegates to {Spree::Orders::Approve} service.
|
|
829
|
+
#
|
|
830
|
+
# @return [Spree::ServiceModule::Result]
|
|
809
831
|
def approve!
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
# Manually publish update event since update_column bypasses callbacks
|
|
813
|
-
publish_event('order.approved')
|
|
832
|
+
Spree.order_approve_service.call(order: self)
|
|
814
833
|
end
|
|
815
834
|
|
|
816
835
|
def tax_total
|
|
@@ -978,6 +997,15 @@ module Spree
|
|
|
978
997
|
self.currency ||= store&.default_currency
|
|
979
998
|
end
|
|
980
999
|
|
|
1000
|
+
def currency_must_be_supported_by_store
|
|
1001
|
+
return if currency.blank? || store.blank?
|
|
1002
|
+
|
|
1003
|
+
supported_codes = store.supported_currencies_list.map(&:iso_code)
|
|
1004
|
+
unless supported_codes.include?(currency)
|
|
1005
|
+
errors.add(:currency, Spree.t(:currency_not_supported_by_store))
|
|
1006
|
+
end
|
|
1007
|
+
end
|
|
1008
|
+
|
|
981
1009
|
def collect_payment_methods
|
|
982
1010
|
Spree::Deprecation.warn('`Order#collect_payment_methods` is deprecated and will be removed in Spree 5.5. Use `collect_frontend_payment_methods` instead.')
|
|
983
1011
|
|
data/app/models/spree/payment.rb
CHANGED
|
@@ -2,9 +2,10 @@ require_dependency 'spree/payment/processing'
|
|
|
2
2
|
|
|
3
3
|
module Spree
|
|
4
4
|
class Payment < Spree.base_class
|
|
5
|
+
has_prefix_id :py # Stripe: py_
|
|
6
|
+
|
|
5
7
|
include Spree::Core::NumberGenerator.new(prefix: 'P', letters: true, length: 7)
|
|
6
8
|
include Spree::NumberIdentifier
|
|
7
|
-
include Spree::NumberAsParam
|
|
8
9
|
include Spree::Metafields
|
|
9
10
|
include Spree::Metadata
|
|
10
11
|
if defined?(Spree::Security::Payments)
|
|
@@ -35,6 +36,10 @@ module Spree
|
|
|
35
36
|
has_many :capture_events, class_name: 'Spree::PaymentCaptureEvent'
|
|
36
37
|
has_many :refunds, inverse_of: :payment
|
|
37
38
|
|
|
39
|
+
has_one :payment_session, class_name: 'Spree::PaymentSession',
|
|
40
|
+
foreign_key: :external_id,
|
|
41
|
+
primary_key: :response_code
|
|
42
|
+
|
|
38
43
|
validates :payment_method, presence: true
|
|
39
44
|
validates :source, presence: true, if: :source_required?
|
|
40
45
|
validate :payment_method_available_for_order, on: :create
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class PaymentMethod < Spree.base_class
|
|
3
|
+
has_prefix_id :pm # Stripe: pm_
|
|
4
|
+
|
|
3
5
|
acts_as_paranoid
|
|
4
6
|
acts_as_list
|
|
5
7
|
|
|
6
|
-
include Spree::
|
|
8
|
+
include Spree::StoreScopedResource
|
|
7
9
|
include Spree::Metafields
|
|
8
10
|
include Spree::Metadata
|
|
9
11
|
include Spree::DisplayOn
|
|
@@ -26,6 +28,8 @@ module Spree
|
|
|
26
28
|
has_many :payments, class_name: 'Spree::Payment', inverse_of: :payment_method, dependent: :nullify
|
|
27
29
|
has_many :credit_cards, class_name: 'Spree::CreditCard', dependent: :destroy # CCs are soft deleted
|
|
28
30
|
|
|
31
|
+
has_many :payment_sessions, class_name: 'Spree::PaymentSession', dependent: :destroy
|
|
32
|
+
has_many :payment_setup_sessions, class_name: 'Spree::PaymentSetupSession', dependent: :destroy
|
|
29
33
|
has_many :gateway_customers, class_name: 'Spree::GatewayCustomer', dependent: :destroy
|
|
30
34
|
|
|
31
35
|
def self.providers
|
|
@@ -45,6 +49,56 @@ module Spree
|
|
|
45
49
|
raise ::NotImplementedError, 'You must implement payment_source_class method for this gateway.'
|
|
46
50
|
end
|
|
47
51
|
|
|
52
|
+
# The class used for payment sessions with this payment method.
|
|
53
|
+
# Override in gateway subclasses to provide a provider-specific session class
|
|
54
|
+
# that inherits from Spree::PaymentSession (STI).
|
|
55
|
+
# nil means the payment method doesn't support payment sessions.
|
|
56
|
+
def payment_session_class
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Creates a payment session via the provider.
|
|
61
|
+
# Override in gateway subclasses to implement provider-specific session creation.
|
|
62
|
+
def create_payment_session(order:, amount: nil, external_data: {})
|
|
63
|
+
raise ::NotImplementedError, 'You must implement create_payment_session method for this gateway.'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Updates an existing payment session via the provider.
|
|
67
|
+
# Override in gateway subclasses to implement provider-specific session updates.
|
|
68
|
+
def update_payment_session(payment_session:, amount: nil, external_data: {})
|
|
69
|
+
raise ::NotImplementedError, 'You must implement update_payment_session method for this gateway.'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Completes a payment session via the provider.
|
|
73
|
+
# Override in gateway subclasses to implement provider-specific session completion.
|
|
74
|
+
def complete_payment_session(payment_session:, params: {})
|
|
75
|
+
raise ::NotImplementedError, 'You must implement complete_payment_session method for this gateway.'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Whether this payment method supports setup sessions (saving payment methods for future use).
|
|
79
|
+
# Override in gateway subclasses that support tokenization without a payment.
|
|
80
|
+
def setup_session_supported?
|
|
81
|
+
false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The class used for payment setup sessions with this payment method.
|
|
85
|
+
# Override in gateway subclasses to provide a provider-specific session class.
|
|
86
|
+
def payment_setup_session_class
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Creates a payment setup session via the provider for saving a payment method.
|
|
91
|
+
# Override in gateway subclasses to implement provider-specific setup session creation.
|
|
92
|
+
def create_payment_setup_session(customer:, external_data: {})
|
|
93
|
+
raise ::NotImplementedError, "#{self.class.name} does not implement #create_payment_setup_session"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Completes a payment setup session via the provider.
|
|
97
|
+
# Override in gateway subclasses to implement provider-specific setup session completion.
|
|
98
|
+
def complete_payment_setup_session(setup_session:, params: {})
|
|
99
|
+
raise ::NotImplementedError, "#{self.class.name} does not implement #complete_payment_setup_session"
|
|
100
|
+
end
|
|
101
|
+
|
|
48
102
|
def method_type
|
|
49
103
|
type.demodulize.downcase
|
|
50
104
|
end
|
|
@@ -73,6 +127,10 @@ module Spree
|
|
|
73
127
|
true
|
|
74
128
|
end
|
|
75
129
|
|
|
130
|
+
def session_required?
|
|
131
|
+
false
|
|
132
|
+
end
|
|
133
|
+
|
|
76
134
|
def show_in_admin?
|
|
77
135
|
true
|
|
78
136
|
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class PaymentSession < Spree.base_class
|
|
3
|
+
has_prefix_id :ps
|
|
4
|
+
|
|
5
|
+
acts_as_paranoid
|
|
6
|
+
|
|
7
|
+
include Spree::Metafields
|
|
8
|
+
|
|
9
|
+
self.event_prefix = 'payment_session'
|
|
10
|
+
|
|
11
|
+
publishes_lifecycle_events
|
|
12
|
+
|
|
13
|
+
belongs_to :order, class_name: 'Spree::Order'
|
|
14
|
+
belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
|
|
15
|
+
belongs_to :customer, class_name: Spree.user_class.to_s, optional: true
|
|
16
|
+
|
|
17
|
+
has_one :payment, class_name: 'Spree::Payment',
|
|
18
|
+
foreign_key: :response_code,
|
|
19
|
+
primary_key: :external_id
|
|
20
|
+
|
|
21
|
+
validates :order, :payment_method, :external_id, :status, :currency, presence: true
|
|
22
|
+
validates :external_id, uniqueness: { scope: [:order_id, :payment_method_id] }
|
|
23
|
+
validates :amount, presence: true, numericality: { greater_than: 0 }
|
|
24
|
+
|
|
25
|
+
state_machine :status, initial: :pending do
|
|
26
|
+
state :pending
|
|
27
|
+
state :processing
|
|
28
|
+
state :completed
|
|
29
|
+
state :failed
|
|
30
|
+
state :canceled
|
|
31
|
+
state :expired
|
|
32
|
+
|
|
33
|
+
event :process do
|
|
34
|
+
transition pending: :processing
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
event :complete do
|
|
38
|
+
transition [:pending, :processing] => :completed
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
event :fail do
|
|
42
|
+
transition [:pending, :processing] => :failed
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
event :cancel do
|
|
46
|
+
transition [:pending, :processing] => :canceled
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
event :expire do
|
|
50
|
+
transition [:pending, :processing] => :expired
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
after_transition to: :processing, do: :publish_processing_event
|
|
54
|
+
after_transition to: :completed, do: :publish_completed_event
|
|
55
|
+
after_transition to: :failed, do: :publish_failed_event
|
|
56
|
+
after_transition to: :canceled, do: :publish_canceled_event
|
|
57
|
+
after_transition to: :expired, do: :publish_expired_event
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
scope :not_expired, -> { where('expires_at IS NULL OR expires_at > ?', Time.current) }
|
|
61
|
+
scope :active, -> { not_expired.where(status: %w[pending processing]) }
|
|
62
|
+
|
|
63
|
+
before_validation :set_defaults_from_order, on: :create
|
|
64
|
+
|
|
65
|
+
delegate :store, to: :order
|
|
66
|
+
|
|
67
|
+
def amount_in_cents
|
|
68
|
+
money.cents
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def money
|
|
72
|
+
@money ||= Spree::Money.new(amount, currency: currency)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def expired?
|
|
76
|
+
expires_at.present? && expires_at <= Time.current
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def publish_processing_event
|
|
82
|
+
publish_event('payment_session.processing')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def publish_completed_event
|
|
86
|
+
publish_event('payment_session.completed')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def publish_failed_event
|
|
90
|
+
publish_event('payment_session.failed')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def publish_canceled_event
|
|
94
|
+
publish_event('payment_session.canceled')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def publish_expired_event
|
|
98
|
+
publish_event('payment_session.expired')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def set_defaults_from_order
|
|
102
|
+
return unless order
|
|
103
|
+
|
|
104
|
+
self.amount ||= order.total_minus_store_credits if amount.blank? || amount.zero?
|
|
105
|
+
self.currency ||= order.currency
|
|
106
|
+
self.customer ||= order.user
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class PaymentSetupSession < Spree.base_class
|
|
3
|
+
has_prefix_id :pss
|
|
4
|
+
|
|
5
|
+
acts_as_paranoid
|
|
6
|
+
|
|
7
|
+
include Spree::Metafields
|
|
8
|
+
|
|
9
|
+
self.event_prefix = 'payment_setup_session'
|
|
10
|
+
|
|
11
|
+
publishes_lifecycle_events
|
|
12
|
+
|
|
13
|
+
belongs_to :customer, class_name: Spree.user_class.to_s, optional: true
|
|
14
|
+
belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
|
|
15
|
+
belongs_to :payment_source, polymorphic: true, optional: true
|
|
16
|
+
|
|
17
|
+
validates :payment_method, :status, presence: true
|
|
18
|
+
validates :external_id, uniqueness: { scope: :payment_method_id }, allow_nil: true
|
|
19
|
+
|
|
20
|
+
state_machine :status, initial: :pending do
|
|
21
|
+
state :pending
|
|
22
|
+
state :processing
|
|
23
|
+
state :completed
|
|
24
|
+
state :failed
|
|
25
|
+
state :canceled
|
|
26
|
+
state :expired
|
|
27
|
+
|
|
28
|
+
event :process do
|
|
29
|
+
transition pending: :processing
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
event :complete do
|
|
33
|
+
transition [:pending, :processing] => :completed
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
event :fail do
|
|
37
|
+
transition [:pending, :processing] => :failed
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
event :cancel do
|
|
41
|
+
transition [:pending, :processing] => :canceled
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
event :expire do
|
|
45
|
+
transition [:pending, :processing] => :expired
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
after_transition to: :processing, do: :publish_processing_event
|
|
49
|
+
after_transition to: :completed, do: :publish_completed_event
|
|
50
|
+
after_transition to: :failed, do: :publish_failed_event
|
|
51
|
+
after_transition to: :canceled, do: :publish_canceled_event
|
|
52
|
+
after_transition to: :expired, do: :publish_expired_event
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
scope :active, -> { where(status: %w[pending processing]) }
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def publish_processing_event
|
|
60
|
+
publish_event('payment_setup_session.processing')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def publish_completed_event
|
|
64
|
+
publish_event('payment_setup_session.completed')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def publish_failed_event
|
|
68
|
+
publish_event('payment_setup_session.failed')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def publish_canceled_event
|
|
72
|
+
publish_event('payment_setup_session.canceled')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def publish_expired_event
|
|
76
|
+
publish_event('payment_setup_session.expired')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# This model is used to store payment sources for non-credit card payments, eg wallet, account, etc.
|
|
2
2
|
module Spree
|
|
3
3
|
class PaymentSource < Spree.base_class
|
|
4
|
+
has_prefix_id :ps
|
|
5
|
+
|
|
4
6
|
include Spree::Metafields
|
|
5
7
|
include Spree::Metadata
|
|
6
8
|
include Spree::PaymentSourceConcern
|
|
@@ -39,6 +39,17 @@ module Spree
|
|
|
39
39
|
!order.completed? && (order.user == user || order.token && token == order.token)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# Line item management
|
|
43
|
+
can :create, Spree::LineItem do |line_item, token|
|
|
44
|
+
line_item.order.user == user || line_item.order.token && token == line_item.order.token
|
|
45
|
+
end
|
|
46
|
+
can :update, Spree::LineItem do |line_item, token|
|
|
47
|
+
!line_item.order.completed? && (line_item.order.user == user || line_item.order.token && token == line_item.order.token)
|
|
48
|
+
end
|
|
49
|
+
can :destroy, Spree::LineItem do |line_item, token|
|
|
50
|
+
!line_item.order.completed? && (line_item.order.user == user || line_item.order.token && token == line_item.order.token)
|
|
51
|
+
end
|
|
52
|
+
|
|
42
53
|
# User account management - available to all users (including guests for their own record)
|
|
43
54
|
can :create, Spree.user_class
|
|
44
55
|
can [:show, :update, :destroy], Spree.user_class, id: user.id
|
|
@@ -49,6 +60,9 @@ module Spree
|
|
|
49
60
|
# Credit card management
|
|
50
61
|
can [:read, :destroy], Spree::CreditCard, user_id: user.id
|
|
51
62
|
|
|
63
|
+
# Gift card management - users can view their own gift cards
|
|
64
|
+
can :read, Spree::GiftCard, user_id: user.id
|
|
65
|
+
|
|
52
66
|
# Wishlist management
|
|
53
67
|
can :manage, Spree::Wishlist, user_id: user.id
|
|
54
68
|
can :show, Spree::Wishlist do |wishlist|
|
|
@@ -60,6 +74,11 @@ module Spree
|
|
|
60
74
|
|
|
61
75
|
# Invitation acceptance
|
|
62
76
|
can :accept, Spree::Invitation, invitee_id: [user.id, nil], invitee_type: user.class.name, status: 'pending'
|
|
77
|
+
|
|
78
|
+
# Digital downloads - token-based access
|
|
79
|
+
can :show, Spree::DigitalLink do |digital_link, token|
|
|
80
|
+
digital_link.token == token
|
|
81
|
+
end
|
|
63
82
|
end
|
|
64
83
|
end
|
|
65
84
|
end
|
data/app/models/spree/policy.rb
CHANGED
data/app/models/spree/post.rb
CHANGED
data/app/models/spree/price.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class Price < Spree.base_class
|
|
3
|
+
has_prefix_id :price
|
|
4
|
+
|
|
3
5
|
include Spree::VatPriceCalculation
|
|
4
6
|
|
|
5
7
|
publishes_lifecycle_events
|
|
@@ -38,13 +40,13 @@ module Spree
|
|
|
38
40
|
scope :discounted, -> { where('compare_at_amount > amount') }
|
|
39
41
|
scope :base_prices, -> { where(price_list_id: nil) }
|
|
40
42
|
scope :for_price_list, ->(price_list) { where(price_list_id: price_list) }
|
|
41
|
-
scope :for_products,
|
|
43
|
+
scope :for_products, lambda { |products, currency = nil|
|
|
42
44
|
currency ||= Spree::Store.default.default_currency
|
|
43
45
|
|
|
44
46
|
with_currency(currency).joins(:variant).where(
|
|
45
47
|
Spree::Variant.table_name => { product_id: products }
|
|
46
48
|
)
|
|
47
|
-
|
|
49
|
+
}
|
|
48
50
|
|
|
49
51
|
extend DisplayMoney
|
|
50
52
|
money_methods :amount, :price, :compare_at_amount
|
|
@@ -64,6 +66,12 @@ module Spree
|
|
|
64
66
|
self[:amount] = amount.blank? ? nil : Spree::LocalizedNumber.parse(amount)
|
|
65
67
|
end
|
|
66
68
|
|
|
69
|
+
# Returns the amount in cents
|
|
70
|
+
# @return [Integer]
|
|
71
|
+
def amount_in_cents
|
|
72
|
+
display_amount&.amount_in_cents
|
|
73
|
+
end
|
|
74
|
+
|
|
67
75
|
def compare_at_money
|
|
68
76
|
Spree::Money.new(compare_at_amount || 0, currency: currency)
|
|
69
77
|
end
|
|
@@ -74,6 +82,22 @@ module Spree
|
|
|
74
82
|
self[:compare_at_amount] = calculated_value
|
|
75
83
|
end
|
|
76
84
|
|
|
85
|
+
# Returns the compare at amount for display
|
|
86
|
+
# @return [Spree::Money, nil]
|
|
87
|
+
def display_compare_at_amount
|
|
88
|
+
return nil if compare_at_amount.nil?
|
|
89
|
+
|
|
90
|
+
Spree::Money.new(compare_at_amount, currency: currency)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns the compare at amount in cents
|
|
94
|
+
# @return [Integer, nil]
|
|
95
|
+
def compare_at_amount_in_cents
|
|
96
|
+
return nil if compare_at_amount.nil?
|
|
97
|
+
|
|
98
|
+
display_compare_at_amount.amount_in_cents
|
|
99
|
+
end
|
|
100
|
+
|
|
77
101
|
alias_attribute :price, :amount
|
|
78
102
|
alias_method :price=, :amount=
|
|
79
103
|
alias_attribute :compare_at_price, :compare_at_amount
|