spree_emails 5.6.1 → 6.0.0.beta1

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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +30 -0
  3. data/README.md +2 -2
  4. data/app/helpers/spree/base_helper.rb +45 -0
  5. data/app/helpers/spree/digital_asset_helper.rb +16 -0
  6. data/app/helpers/spree/fulfillment_helper.rb +12 -0
  7. data/app/helpers/spree/mail_helper.rb +0 -13
  8. data/app/mailers/spree/company_mailer.rb +40 -0
  9. data/app/mailers/spree/customer_mailer.rb +21 -0
  10. data/app/mailers/spree/digital_asset_mailer.rb +30 -0
  11. data/app/mailers/spree/fulfillment_mailer.rb +17 -0
  12. data/app/mailers/spree/order_mailer.rb +1 -1
  13. data/app/mailers/spree/return_mailer.rb +17 -0
  14. data/app/mailers/spree/seller_mailer.rb +68 -0
  15. data/app/mailers/spree/shipment_mailer.rb +4 -11
  16. data/app/subscribers/spree/company_email_subscriber.rb +19 -0
  17. data/app/subscribers/spree/customer_email_subscriber.rb +1 -1
  18. data/app/subscribers/spree/data_request_email_subscriber.rb +21 -0
  19. data/app/subscribers/spree/digital_asset_email_subscriber.rb +35 -0
  20. data/app/subscribers/spree/fulfillment_email_subscriber.rb +34 -0
  21. data/app/subscribers/spree/order_email_subscriber.rb +5 -2
  22. data/app/subscribers/spree/return_email_subscriber.rb +23 -0
  23. data/app/subscribers/spree/seller_email_subscriber.rb +46 -0
  24. data/app/views/spree/company_mailer/invitation_email.html.erb +8 -0
  25. data/app/views/spree/company_mailer/invitation_email.text.erb +11 -0
  26. data/app/views/spree/customer_mailer/data_export_email.html.erb +12 -0
  27. data/app/views/spree/customer_mailer/data_export_email.text.erb +9 -0
  28. data/app/views/spree/digital_asset_mailer/files_ready_email.html.erb +20 -0
  29. data/app/views/spree/digital_asset_mailer/files_ready_email.text.erb +10 -0
  30. data/app/views/spree/fulfillment_mailer/fulfilled_email.html.erb +22 -0
  31. data/app/views/spree/fulfillment_mailer/fulfilled_email.text.erb +18 -0
  32. data/app/views/spree/order_mailer/cancel_email.text.erb +1 -0
  33. data/app/views/spree/order_mailer/confirm_email.text.erb +1 -0
  34. data/app/views/spree/order_mailer/store_owner_notification_email.text.erb +1 -0
  35. data/app/views/spree/return_mailer/refunded_email.html.erb +22 -0
  36. data/app/views/spree/return_mailer/refunded_email.text.erb +20 -0
  37. data/app/views/spree/seller_mailer/approved_email.html.erb +8 -0
  38. data/app/views/spree/seller_mailer/approved_email.text.erb +8 -0
  39. data/app/views/spree/seller_mailer/rejected_email.html.erb +6 -0
  40. data/app/views/spree/seller_mailer/rejected_email.text.erb +7 -0
  41. data/app/views/spree/seller_mailer/suspended_email.html.erb +6 -0
  42. data/app/views/spree/seller_mailer/suspended_email.text.erb +7 -0
  43. data/app/views/spree/shared/_item_thumbnail_link.html.erb +12 -1
  44. data/app/views/spree/shared/_order_summary_section.html.erb +5 -0
  45. data/app/views/spree/shared/_po_number.text.erb +6 -0
  46. data/app/views/spree/shared/_purchased_items_table.html.erb +16 -11
  47. data/app/views/spree/shared/_purchased_items_table.text.erb +9 -10
  48. data/lib/spree/emails/engine.rb +4 -3
  49. data/lib/spree/emails/previews/fulfillment_preview.rb +12 -0
  50. data/lib/spree/emails/previews/return_preview.rb +12 -0
  51. metadata +43 -28
  52. data/app/assets/config/spree_emails_manifest.js +0 -1
  53. data/app/assets/images/logo/spree_50.png +0 -0
  54. data/app/assets/images/noimage/mini.png +0 -0
  55. data/app/assets/images/noimage/noimage/large.png +0 -0
  56. data/app/assets/images/noimage/noimage/mini.png +0 -0
  57. data/app/assets/images/noimage/noimage/product.png +0 -0
  58. data/app/assets/images/noimage/noimage/small.png +0 -0
  59. data/app/assets/images/noimage/small.png +0 -0
  60. data/app/mailers/spree/reimbursement_mailer.rb +0 -15
  61. data/app/subscribers/spree/reimbursement_email_subscriber.rb +0 -24
  62. data/app/subscribers/spree/shipment_email_subscriber.rb +0 -24
  63. data/app/views/spree/reimbursement_mailer/reimbursement_email.html.erb +0 -47
  64. data/app/views/spree/reimbursement_mailer/reimbursement_email.text.erb +0 -24
  65. data/app/views/spree/shared/purchased_items_table/_adjustment.html.erb +0 -3
  66. data/app/views/spree/shipment_mailer/shipped_email.html.erb +0 -20
  67. data/app/views/spree/shipment_mailer/shipped_email.text.erb +0 -17
  68. data/config/initializers/assets.rb +0 -3
  69. data/lib/spree/emails/previews/reimbursement_preview.rb +0 -12
  70. data/lib/spree/emails/previews/shipment_preview.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed4f382283e6b57600f548ec3ec8c5d210c1d4e5cf918fe1e20b5d8f5d71f0e7
4
- data.tar.gz: 1d0e7dc98a9e1e3bf2d9fe287c977256231ca6ae7ea389534784e6650d1bf070
3
+ metadata.gz: f1bfa005e59ba23cd5784e52fe434f5d7e77d0d76002aaaca68de2f8107f5ed6
4
+ data.tar.gz: '069b0d6d771268fd9c24ae09dead90ef6c803772a9f3bf62db28930f8cf2fea7'
5
5
  SHA512:
6
- metadata.gz: 5aeea6bb70e99acd7f78c2adc4faa80a4f9f8673ec00dd96a887bbfc0b23c22fd69f05a0b17a5357a379363684f91478657d9c6639a0e0ff7077c866dd3d09b2
7
- data.tar.gz: bfd7392f1750101451a9370cd0b933b67bdbeb3efef1aa45dce986a37fe4091732015945b377d9a061ee0b82f31b30a0a008ac86115a7c9491feb7c6750d1da0
6
+ metadata.gz: 136a085fd33cb272a3cc1df61ea9c7a4bdda8e56698381a1ddd86bee9d003c4378a2e36bf6ce4ca9ea400918c604cc0a0e1218d78095af2a5019e059e62f80f9
7
+ data.tar.gz: c8568259e1bbbdb9b243cd04a6b8fbd5c3f13620a2e1fc9d91eefc417067d88647c9b8d7ba7873ef8008886d9ac2b7d1197e03f17757dcdbcff98d6f5b27a7ab
data/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024-present, Vendo Sp. z o.o., Vendo Connect Inc.
4
+ Copyright (c) 2015-2024, Spark Solutions Sp. z o.o.
5
+ Copyright (c) 2007-2015, Spree Commerce Inc.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice,
11
+ this list of conditions and the following disclaimer.
12
+
13
+ 2. Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ 3. Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md CHANGED
@@ -149,7 +149,7 @@ module MyApp
149
149
 
150
150
  def handle(event)
151
151
  user_id = event.payload['id']
152
- user = Spree.user_class.find_by(id: user_id)
152
+ user = Spree.customer_class.find_by(id: user_id)
153
153
  return unless user
154
154
 
155
155
  Spree::CustomMailer.welcome_email(user).deliver_later
@@ -209,4 +209,4 @@ bundle exec rspec
209
209
  ## Documentation
210
210
 
211
211
  - [Email Customization Guide](https://docs.spreecommerce.org/developer/customization/emails)
212
- - [Events System](https://docs.spreecommerce.org/developer/core-concepts/events)
212
+ - [Events System](https://docs.spreecommerce.org/developer/core-concepts/events)
@@ -0,0 +1,45 @@
1
+ module Spree
2
+ module BaseHelper
3
+ # returns the URL of an object on the storefront
4
+ # @param resource [Spree::Product, Spree::Category] the resource to get the URL for
5
+ # @param options [Hash] the options for the URL
6
+ # @option options [String] :locale the locale of the resource, defaults to I18n.locale
7
+ # @option options [String] :store the store of the resource, defaults to current_store
8
+ # @option options [String] :relative whether to use the relative URL, defaults to false
9
+ # @option options [String] :preview_id the preview ID of the resource, usually the ID of the resource
10
+ # @option options [String] :variant_id the variant ID of the resource, usually the ID of the variant (only used for products)
11
+ # @return [String] the URL of the resource
12
+ def spree_storefront_resource_url(resource, options = {})
13
+ store = options[:store] || current_store
14
+
15
+ base_url = if options[:relative]
16
+ ''
17
+ else
18
+ store.storefront_url
19
+ end
20
+
21
+ localize = if options[:locale].present?
22
+ "/#{options[:locale]}"
23
+ else
24
+ ''
25
+ end
26
+
27
+ if resource.instance_of?(Spree::Product)
28
+ preview_id = ("preview_id=#{options[:preview_id]}" if options[:preview_id].present?)
29
+
30
+ variant_id = ("variant_id=#{options[:variant_id]}" if options[:variant_id].present?)
31
+
32
+ params = [preview_id, variant_id].compact_blank.join('&')
33
+ params = "?#{params}" if params.present?
34
+
35
+ "#{base_url + localize}/products/#{resource.slug}#{params}"
36
+ elsif resource.is_a?(Spree::Category)
37
+ "#{base_url + localize}/t/#{resource.permalink}"
38
+ elsif localize.blank?
39
+ base_url
40
+ else
41
+ base_url + localize
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ module Spree
2
+ module DigitalAssetHelper
3
+ # Emails need an absolute link — the route helper only produces a path, and
4
+ # the download endpoint lives on the backend rather than the storefront.
5
+ #
6
+ # The host is passed in rather than resolved per link: every link in one
7
+ # email belongs to the same order, so its store is already known.
8
+ #
9
+ # @param digital_link [Spree::DigitalLink]
10
+ # @param host [String] the backend's own URL
11
+ # @return [String]
12
+ def digital_link_download_url(digital_link, host)
13
+ Spree::Api::DigitalLinkUrls.download_url(digital_link, host)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module Spree
2
+ module FulfillmentHelper
3
+ def fulfillment_tracking_link_to(fulfillment:, name: nil, html_options: {})
4
+ tracking_url = fulfillment.tracking_url.presence
5
+ return '' unless tracking_url
6
+
7
+ display_text = name || fulfillment.tracking.presence || tracking_url
8
+
9
+ link_to display_text, tracking_url, html_options
10
+ end
11
+ end
12
+ end
@@ -3,21 +3,8 @@ module Spree
3
3
  include Spree::BaseHelper
4
4
  include Spree::ImagesHelper
5
5
 
6
- def variant_image_url(variant)
7
- Spree::Deprecation.warn(
8
- 'Spree::MailHelper#variant_image_url is deprecated. Use spree_image_url instead. Will be removed in Spree 6.0.'
9
- )
10
-
11
- image = variant.primary_media
12
- image.present? && image.attached? ? spree_image_url(image, variant: :mini) : image_url('noimage/small.png')
13
- end
14
-
15
6
  def name_for(order)
16
7
  order.name || Spree.t('customer')
17
8
  end
18
-
19
- def store_logo
20
- @store_logo ||= current_store&.mailer_logo || current_store&.logo
21
- end
22
9
  end
23
10
  end
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ # Functional mail for the company directory — currently the invitation that
3
+ # brings a not-yet-registered buyer into a company node. Like the staff
4
+ # invitation, this is transactional, not marketing: the no-welcome-email
5
+ # rule on customer creation is untouched.
6
+ class CompanyMailer < BaseMailer
7
+ helper Spree::MailHelper
8
+
9
+ # The invite carries the plaintext token; the storefront owns the
10
+ # acceptance page, so the link resolves against the store URL like other
11
+ # customer-facing links.
12
+ #
13
+ # @param invitation [Spree::CompanyInvitation, Integer]
14
+ def invitation_email(invitation)
15
+ @invitation = invitation.respond_to?(:id) ? invitation : Spree::CompanyInvitation.find(invitation)
16
+ # Re-checked here, not only where the job was enqueued: a queue can sit
17
+ # for minutes, and an invitation revoked in that window must not still
18
+ # arrive carrying a working token.
19
+ return message.perform_deliveries = false unless @invitation.reload.pending?
20
+
21
+ @company = @invitation.company
22
+ store = @company.store
23
+ @accept_url = append_token(invitation_base_url(store), @invitation.token)
24
+
25
+ with_store_locale(store) do
26
+ mail(
27
+ to: @invitation.email,
28
+ subject: "#{store.name} #{Spree.t('company_mailer.invitation_email.subject', company: @company.name)}",
29
+ store_url: store.storefront_url
30
+ )
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def invitation_base_url(store)
37
+ "#{store.storefront_url.to_s.chomp('/')}/account/company-invitation"
38
+ end
39
+ end
40
+ end
@@ -17,5 +17,26 @@ module Spree
17
17
  )
18
18
  end
19
19
  end
20
+
21
+ # The finished subject access export (GDPR Art. 15). The link is signed
22
+ # and expires with the request, so the file is reachable by the person who
23
+ # asked for it and not by anyone who later reads the mailbox.
24
+ def data_export_email(data_request)
25
+ @data_request = data_request
26
+ @current_store = data_request.store
27
+ # A store route rather than a raw storage URL: a signed storage link
28
+ # cannot be built off-request on the Disk service, and mailing one would
29
+ # put a direct object address in an inbox where it outlives the request.
30
+ @download_url = Spree::Api::DataRequestUrls.download_url(data_request, @current_store.formatted_url)
31
+ @expires_at = data_request.expires_at
32
+
33
+ with_store_locale(@current_store) do
34
+ mail(
35
+ to: data_request.email,
36
+ subject: "#{@current_store.name} #{Spree.t('customer_mailer.data_export_email.subject')}",
37
+ store_url: @current_store.storefront_url
38
+ )
39
+ end
40
+ end
20
41
  end
21
42
  end
@@ -0,0 +1,30 @@
1
+ module Spree
2
+ class DigitalAssetMailer < BaseMailer
3
+ helper Spree::MailHelper
4
+ helper Spree::DigitalAssetHelper
5
+
6
+ # Sent once an order's downloads are ready. Kept separate from the order
7
+ # confirmation so it can be re-sent on its own, and so a storefront that
8
+ # replaces the confirmation email does not silently lose the links.
9
+ def files_ready_email(order, resend = false)
10
+ @order = order.respond_to?(:id) ? order : Spree::Order.find(order)
11
+ # The template reads each link's filename and expiry, both of which reach
12
+ # through the asset — preload rather than paying per link.
13
+ @digital_links = @order.digital_links.includes(digital_asset: { attachment_attachment: :blob })
14
+ return if @digital_links.empty?
15
+
16
+ current_store = @order.store
17
+ @download_host = current_store.formatted_url
18
+ with_store_locale(current_store, @order.locale) do
19
+ subject = order_email_subject(
20
+ current_store,
21
+ Spree.t('digital_asset_mailer.files_ready_email.subject'),
22
+ @order.number,
23
+ resend: resend
24
+ )
25
+ mail(to: @order.email, subject: subject, store_url: current_store.storefront_url,
26
+ template_path: 'spree/digital_asset_mailer', template_name: 'files_ready_email')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ class FulfillmentMailer < BaseMailer
3
+ helper Spree::MailHelper
4
+ helper Spree::FulfillmentHelper
5
+
6
+ def fulfilled_email(fulfillment, resend = false)
7
+ @fulfillment = fulfillment.respond_to?(:id) ? fulfillment : Spree::Fulfillment.find(fulfillment)
8
+ @order = @fulfillment.order
9
+ current_store = @fulfillment.store
10
+ with_store_locale(current_store, @order.locale) do
11
+ subject = order_email_subject(current_store, Spree.t('fulfillment_mailer.fulfilled_email.subject'), @order.number, resend: resend)
12
+ mail(to: @order.email, subject: subject, store_url: current_store.storefront_url,
13
+ template_path: 'spree/fulfillment_mailer', template_name: 'fulfilled_email')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -32,7 +32,7 @@ module Spree
32
32
  def payment_link_email(order_id)
33
33
  @order = Spree::Order.incomplete.not_canceled.find(order_id)
34
34
  @current_store = @order.store
35
- @checkout_payment_url = spree.checkout_state_url(@order.token, :payment, host: @current_store.storefront_url)
35
+ @checkout_payment_url = URI.join(@current_store.storefront_url, "/checkout/#{@order.token}/payment").to_s
36
36
 
37
37
  with_store_locale(@current_store, @order.locale) do
38
38
  mail(to: @order.email, subject: Spree.t('order_mailer.payment_link_email.subject', number: @order.number),
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ class ReturnMailer < BaseMailer
3
+ helper Spree::MailHelper
4
+
5
+ # Tells the customer their returned items were refunded. Replaces the
6
+ # reimbursement email dropped with the ReturnAuthorization chain in 6.0.
7
+ def refunded_email(return_record, resend = false)
8
+ @return = return_record.respond_to?(:id) ? return_record : Spree::Return.find(return_record)
9
+ @order = @return.order
10
+ current_store = @return.store || Spree::Store.default
11
+ with_store_locale(current_store, @order.locale) do
12
+ subject = order_email_subject(current_store, Spree.t('return_mailer.refunded_email.subject'), @order.number, resend: resend)
13
+ mail(to: @order.email, subject: subject, store_url: current_store.storefront_url)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,68 @@
1
+ module Spree
2
+ # Tells a seller what just happened to them on the marketplace.
3
+ #
4
+ # Deliberately seller-facing only: the legacy module also copied every store
5
+ # admin on approvals and onboarding, which on a marketplace with many sellers
6
+ # is a lot of mail about something staff can already see in the dashboard.
7
+ # Marketplace-side notifications ride the event and webhook surface instead.
8
+ class SellerMailer < BaseMailer
9
+ helper Spree::MailHelper
10
+
11
+ # @param seller [Spree::Seller, Integer]
12
+ def approved_email(seller)
13
+ @seller = load_seller(seller)
14
+ # Where the seller signs in. Resolved rather than built from the store URL
15
+ # so a hosted dashboard, a dev Vite server and a mounted build all work.
16
+ @dashboard_url = Spree::Stores::DashboardUrl.call(store: store).presence
17
+
18
+ deliver_to_seller('seller_mailer.approved_email.subject')
19
+ end
20
+
21
+ # Carries no reason: the operator's note is an internal record, and a
22
+ # suspended seller is told to get in touch rather than handed a verdict.
23
+ def suspended_email(seller)
24
+ @seller = load_seller(seller)
25
+
26
+ deliver_to_seller('seller_mailer.suspended_email.subject')
27
+ end
28
+
29
+ def rejected_email(seller)
30
+ @seller = load_seller(seller)
31
+
32
+ deliver_to_seller('seller_mailer.rejected_email.subject')
33
+ end
34
+
35
+ private
36
+
37
+ def load_seller(seller)
38
+ seller.respond_to?(:id) ? seller : Spree::Seller.find(seller)
39
+ end
40
+
41
+ def store
42
+ @store ||= @seller.store || Spree::Store.default
43
+ end
44
+
45
+ # The team plus the address the seller gave for contact — before anyone has
46
+ # accepted an invitation the team is empty, and that address is the only way
47
+ # to reach them.
48
+ def recipients
49
+ (@seller.users.pluck(:email) << @seller.contact_email).compact_blank.uniq(&:downcase)
50
+ end
51
+
52
+ # Takes the key, not the translated string: resolving the subject outside
53
+ # the block would render it in whatever locale the job happens to run
54
+ # under, so a seller could get an English subject over a German email.
55
+ def deliver_to_seller(subject_key)
56
+ addresses = recipients
57
+ return message.perform_deliveries = false if addresses.empty?
58
+
59
+ with_store_locale(store) do
60
+ mail(
61
+ to: addresses,
62
+ subject: Spree.t(subject_key, store_name: store.name),
63
+ store_url: store.storefront_url
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,16 +1,9 @@
1
1
  module Spree
2
- class ShipmentMailer < BaseMailer
3
- helper Spree::MailHelper
4
- helper Spree::ShipmentHelper
5
-
2
+ # Deprecated: use Spree::FulfillmentMailer. Removed in Spree 6.1.
3
+ class ShipmentMailer < FulfillmentMailer
6
4
  def shipped_email(shipment, resend = false)
7
- @shipment = shipment.respond_to?(:id) ? shipment : Spree::Shipment.find(shipment)
8
- @order = @shipment.order
9
- current_store = @shipment.store
10
- with_store_locale(current_store, @order.locale) do
11
- subject = order_email_subject(current_store, Spree.t('shipment_mailer.shipped_email.subject'), @order.number, resend: resend)
12
- mail(to: @order.email, subject: subject, store_url: current_store.storefront_url)
13
- end
5
+ Spree::Deprecation.warn('Spree::ShipmentMailer#shipped_email is deprecated and will be removed in Spree 6.1. Use Spree::FulfillmentMailer#fulfilled_email instead.')
6
+ fulfilled_email(shipment, resend)
14
7
  end
15
8
  end
16
9
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Sends the company invitation mail when an invitation is created. Riding
5
+ # the event rather than the write path lets a host app silence it (consumer
6
+ # transactional emails off) and send its own instead.
7
+ class CompanyEmailSubscriber < Spree::Subscriber
8
+ subscribes_to 'company_invitation.created'
9
+
10
+ def handle(event)
11
+ invitation = Spree::CompanyInvitation.find_by_prefix_id(event.payload['id'])
12
+ return unless invitation
13
+ return unless invitation.pending?
14
+ return unless invitation.store&.prefers_send_consumer_transactional_emails?
15
+
16
+ CompanyMailer.invitation_email(invitation.id).deliver_later
17
+ end
18
+ end
19
+ end
@@ -5,7 +5,7 @@ module Spree
5
5
  subscribes_to 'customer.password_reset_requested'
6
6
 
7
7
  def handle(event)
8
- user = Spree.user_class.find_by(email: event.payload['email'])
8
+ user = Spree.customer_class.find_by(email: event.payload['email'])
9
9
  return unless user
10
10
 
11
11
  store = find_store(event)
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Emails a finished subject access export to the person who asked for it.
5
+ #
6
+ # Erasure requests send nothing: the account's email address has just been
7
+ # replaced with an undeliverable one, and writing to the old address would
8
+ # re-record the very contact detail the request asked to remove.
9
+ class DataRequestEmailSubscriber < Spree::Subscriber
10
+ subscribes_to 'data_request.completed'
11
+
12
+ def handle(event)
13
+ data_request = Spree::DataRequest.find_by_prefix_id(event.payload['id'])
14
+ return unless data_request&.access?
15
+ return unless data_request.downloadable?
16
+ return unless data_request.store.prefers_send_consumer_transactional_emails?
17
+
18
+ CustomerMailer.data_export_email(data_request).deliver_later
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class DigitalAssetEmailSubscriber < Spree::Subscriber
5
+ # Links are created during completion, before order.placed fires, so the
6
+ # order already knows everything the email needs.
7
+ subscribes_to 'order.placed', 'order.resend_digital_links_email'
8
+
9
+ on 'order.placed', :send_files_ready_email
10
+ on 'order.resend_digital_links_email', :resend_files_ready_email
11
+
12
+ private
13
+
14
+ def send_files_ready_email(event)
15
+ deliver(event)
16
+ end
17
+
18
+ def resend_files_ready_email(event)
19
+ deliver(event, resend: true)
20
+ end
21
+
22
+ def deliver(event, resend: false)
23
+ order = find_order(event)
24
+ return unless order
25
+ return if order.digital_links.empty?
26
+ return unless order.store.prefers_send_consumer_transactional_emails?
27
+
28
+ DigitalAssetMailer.files_ready_email(order.id, resend).deliver_later
29
+ end
30
+
31
+ def find_order(event)
32
+ Spree::Order.find_by_prefix_id(event.payload['id'])
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class FulfillmentEmailSubscriber < Spree::Subscriber
5
+ subscribes_to 'fulfillment.fulfilled'
6
+
7
+ def handle(event)
8
+ return unless notify_customer?(event)
9
+
10
+ fulfillment = find_fulfillment(event)
11
+ return unless fulfillment
12
+
13
+ store = fulfillment.store
14
+ return unless store.prefers_send_consumer_transactional_emails?
15
+
16
+ FulfillmentMailer.fulfilled_email(fulfillment.id).deliver_later
17
+ end
18
+
19
+ private
20
+
21
+ # An admin shipping from the backoffice can suppress the shipment email for
22
+ # this one dispatch (a correction, a re-ship, goods handed over in person).
23
+ # Absent metadata means send, so events published by anything unaware of
24
+ # the flag keep the historic behavior.
25
+ def notify_customer?(event)
26
+ event.metadata.fetch('notify_customer', true) != false
27
+ end
28
+
29
+ def find_fulfillment(event)
30
+ fulfillment_id = event.payload['id']
31
+ Spree::Fulfillment.find_by_prefix_id(fulfillment_id)
32
+ end
33
+ end
34
+ end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Spree
4
4
  class OrderEmailSubscriber < Spree::Subscriber
5
- subscribes_to 'order.completed', 'order.canceled', 'order.resend_confirmation_email'
5
+ subscribes_to 'order.placed', 'order.canceled', 'order.resend_confirmation_email'
6
6
 
7
- on 'order.completed', :send_confirmation_email
7
+ on 'order.placed', :send_confirmation_email
8
8
  on 'order.canceled', :send_cancel_email
9
9
  on 'order.resend_confirmation_email', :resend_confirmation_email
10
10
 
@@ -41,6 +41,9 @@ module Spree
41
41
  return unless order
42
42
  return if event.payload['notify_customer'] == false
43
43
 
44
+ store = order.store
45
+ return unless store.prefers_send_consumer_transactional_emails?
46
+
44
47
  OrderMailer.cancel_email(order.id).deliver_later
45
48
  end
46
49
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class ReturnEmailSubscriber < Spree::Subscriber
5
+ subscribes_to 'return.refunded'
6
+
7
+ def handle(event)
8
+ return_record = find_return(event)
9
+ return unless return_record
10
+
11
+ store = return_record.store
12
+ return unless store.prefers_send_consumer_transactional_emails?
13
+
14
+ ReturnMailer.refunded_email(return_record.id).deliver_later
15
+ end
16
+
17
+ private
18
+
19
+ def find_return(event)
20
+ Spree::Return.find_by_prefix_id(event.payload['id'])
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Turns the seller lifecycle events into seller-facing mail.
5
+ #
6
+ # Nothing is sent from the workflows themselves: a transition publishes an
7
+ # event, and mail is one listener among several (webhooks, analytics) rather
8
+ # than a step the transition waits on.
9
+ #
10
+ # `seller.invited` is deliberately absent — inviting a seller creates an
11
+ # Invitation, which already mails the invitee through InvitationEmailSubscriber.
12
+ # A second email would be the same news to the same address.
13
+ class SellerEmailSubscriber < Spree::Subscriber
14
+ subscribes_to 'seller.approved', 'seller.suspended', 'seller.rejected'
15
+
16
+ on 'seller.approved', :send_approved_email
17
+ on 'seller.suspended', :send_suspended_email
18
+ on 'seller.rejected', :send_rejected_email
19
+
20
+ private
21
+
22
+ def send_approved_email(event)
23
+ deliver(event, :approved_email)
24
+ end
25
+
26
+ def send_suspended_email(event)
27
+ deliver(event, :suspended_email)
28
+ end
29
+
30
+ def send_rejected_email(event)
31
+ deliver(event, :rejected_email)
32
+ end
33
+
34
+ def deliver(event, mailer_method)
35
+ seller = find_seller(event)
36
+ return unless seller
37
+ return unless seller.store&.prefers_send_seller_transactional_emails?
38
+
39
+ SellerMailer.public_send(mailer_method, seller.id).deliver_later
40
+ end
41
+
42
+ def find_seller(event)
43
+ Spree::Seller.find_by_prefix_id(event.payload['id'])
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,8 @@
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('company_mailer.invitation_email.title', company: @company.name) %></h1>
3
+ <p class="greeting"><%= Spree.t('company_mailer.invitation_email.greeting') %></p>
4
+ <p><%= Spree.t('company_mailer.invitation_email.instructions', company: @company.name, store: current_store.name) %></p>
5
+ <%= render 'spree/shared/mailer_button', url: @accept_url, label: Spree.t('company_mailer.invitation_email.action') %>
6
+ <p class="sub"><%= Spree.t('company_mailer.invitation_email.expiry_notice') %></p>
7
+ <p class="sub"><%= Spree.t('company_mailer.invitation_email.ignore_notice') %></p>
8
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <%= Spree.t('company_mailer.invitation_email.greeting') %>
2
+
3
+ <%= Spree.t('company_mailer.invitation_email.instructions', company: @company.name, store: current_store.name) %>
4
+
5
+ <%= Spree.t('company_mailer.invitation_email.action') %>: <%= @accept_url %>
6
+
7
+ <%= Spree.t('company_mailer.invitation_email.expiry_notice') %>
8
+
9
+ <%= Spree.t('company_mailer.invitation_email.ignore_notice') %>
10
+
11
+ <%= Spree.t('order_mailer.store_team', store_name: current_store.name) %>
@@ -0,0 +1,12 @@
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('customer_mailer.data_export_email.action') %></h1>
3
+ <p class="greeting"><%= Spree.t('customer_mailer.data_export_email.greeting') %></p>
4
+ <p><%= Spree.t('customer_mailer.data_export_email.instructions') %></p>
5
+ <% if @download_url %>
6
+ <%= render 'spree/shared/mailer_button', url: @download_url, label: Spree.t('customer_mailer.data_export_email.action') %>
7
+ <% end %>
8
+ <% if @expires_at %>
9
+ <p class="sub"><%= Spree.t('customer_mailer.data_export_email.expiry_notice', date: I18n.l(@expires_at.to_date, format: :long)) %></p>
10
+ <% end %>
11
+ <p class="sub"><%= Spree.t('customer_mailer.data_export_email.ignore_notice') %></p>
12
+ <% end %>