spree_emails 6.0.0.beta4 → 6.0.0.rc1

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -74
  3. data/Rakefile +23 -0
  4. data/app/mailers/spree/company_mailer.rb +4 -7
  5. data/app/mailers/spree/customer_mailer.rb +6 -8
  6. data/app/mailers/spree/digital_asset_mailer.rb +18 -11
  7. data/app/mailers/spree/fulfillment_mailer.rb +11 -7
  8. data/app/mailers/spree/newsletter_mailer.rb +1 -2
  9. data/app/mailers/spree/order_group_mailer.rb +16 -20
  10. data/app/mailers/spree/order_mailer.rb +15 -20
  11. data/app/mailers/spree/return_mailer.rb +10 -5
  12. data/app/mailers/spree/seller_mailer.rb +8 -13
  13. data/app/models/spree/emails/amount_line.rb +32 -0
  14. data/app/serializers/spree/emails/address_serializer.rb +6 -0
  15. data/app/serializers/spree/emails/amount_line_serializer.rb +17 -0
  16. data/app/serializers/spree/emails/applied_promotion_serializer.rb +6 -0
  17. data/app/serializers/spree/emails/delivery_method_serializer.rb +6 -0
  18. data/app/serializers/spree/emails/delivery_rate_serializer.rb +10 -0
  19. data/app/serializers/spree/emails/delivery_serializer.rb +6 -0
  20. data/app/serializers/spree/emails/fee_serializer.rb +6 -0
  21. data/app/serializers/spree/emails/freight_summary_serializer.rb +6 -0
  22. data/app/serializers/spree/emails/fulfillment_group_serializer.rb +22 -0
  23. data/app/serializers/spree/emails/fulfillment_serializer.rb +19 -0
  24. data/app/serializers/spree/emails/gift_card_serializer.rb +6 -0
  25. data/app/serializers/spree/emails/line_item_serializer.rb +22 -0
  26. data/app/serializers/spree/emails/market_serializer.rb +6 -0
  27. data/app/serializers/spree/emails/option_value_serializer.rb +6 -0
  28. data/app/serializers/spree/emails/order_group_serializer.rb +60 -0
  29. data/app/serializers/spree/emails/order_serializer.rb +25 -0
  30. data/app/serializers/spree/emails/parcel_item_serializer.rb +36 -0
  31. data/app/serializers/spree/emails/payment_method_serializer.rb +6 -0
  32. data/app/serializers/spree/emails/payment_serializer.rb +9 -0
  33. data/app/serializers/spree/emails/purchase_summary_attributes.rb +41 -0
  34. data/app/serializers/spree/emails/purchased_item_attributes.rb +28 -0
  35. data/app/serializers/spree/emails/return_serializer.rb +17 -0
  36. data/app/serializers/spree/emails/stock_location_serializer.rb +6 -0
  37. data/app/services/spree/emails/samples/base.rb +79 -0
  38. data/app/services/spree/emails/samples/company_invitation.rb +19 -0
  39. data/app/services/spree/emails/samples/data_export.rb +11 -0
  40. data/app/services/spree/emails/samples/downloads.rb +28 -0
  41. data/app/services/spree/emails/samples/fulfillment.rb +33 -0
  42. data/app/services/spree/emails/samples/newsletter_confirmation.rb +11 -0
  43. data/app/services/spree/emails/samples/order.rb +22 -0
  44. data/app/services/spree/emails/samples/order_group.rb +22 -0
  45. data/app/services/spree/emails/samples/password_reset.rb +15 -0
  46. data/app/services/spree/emails/samples/payment_link.rb +17 -0
  47. data/app/services/spree/emails/samples/return.rb +29 -0
  48. data/app/subscribers/spree/order_email_subscriber.rb +2 -2
  49. data/app/subscribers/spree/order_group_email_subscriber.rb +1 -1
  50. data/app/subscribers/spree/return_email_subscriber.rb +2 -0
  51. data/app/views/spree/company_mailer/invitation_email.liquid +13 -0
  52. data/app/views/spree/customer_mailer/data_export_email.liquid +18 -0
  53. data/app/views/spree/customer_mailer/password_reset_email.liquid +14 -0
  54. data/app/views/spree/digital_asset_mailer/files_ready_email.liquid +23 -0
  55. data/app/views/spree/fulfillment_mailer/fulfilled_email.liquid +21 -0
  56. data/app/views/spree/newsletter_mailer/email_confirmation.liquid +13 -0
  57. data/app/views/spree/order_group_mailer/confirm_email.liquid +35 -0
  58. data/app/views/spree/order_group_mailer/store_owner_notification_email.liquid +11 -0
  59. data/app/views/spree/order_mailer/cancel_email.liquid +12 -0
  60. data/app/views/spree/order_mailer/confirm_email.liquid +12 -0
  61. data/app/views/spree/order_mailer/payment_link_email.liquid +12 -0
  62. data/app/views/spree/order_mailer/store_owner_notification_email.liquid +11 -0
  63. data/app/views/spree/return_mailer/refunded_email.liquid +18 -0
  64. data/app/views/spree/seller_mailer/approved_email.liquid +13 -0
  65. data/app/views/spree/seller_mailer/rejected_email.liquid +11 -0
  66. data/app/views/spree/seller_mailer/suspended_email.liquid +11 -0
  67. data/app/views/spree/shared/_fulfillment_group.liquid +20 -0
  68. data/app/views/spree/shared/_line_item.liquid +29 -0
  69. data/app/views/spree/shared/_order_summary.liquid +17 -0
  70. data/app/views/spree/shared/_purchase_totals.liquid +28 -0
  71. data/config/initializers/typelizer.rb +60 -0
  72. data/config/locales/ar.yml +40 -0
  73. data/config/locales/be.yml +37 -0
  74. data/config/locales/bg.yml +31 -0
  75. data/config/locales/ca.yml +35 -0
  76. data/config/locales/cs.yml +38 -0
  77. data/config/locales/da.yml +37 -0
  78. data/config/locales/de-CH.yml +11 -0
  79. data/config/locales/de.yml +38 -0
  80. data/config/locales/el.yml +37 -0
  81. data/config/locales/en.yml +0 -25
  82. data/config/locales/es-CL.yml +21 -0
  83. data/config/locales/es-EC.yml +22 -0
  84. data/config/locales/es-MX.yml +26 -0
  85. data/config/locales/es.yml +40 -0
  86. data/config/locales/et.yml +31 -0
  87. data/config/locales/fa.yml +37 -0
  88. data/config/locales/fi.yml +37 -0
  89. data/config/locales/fr.yml +38 -0
  90. data/config/locales/hu.yml +37 -0
  91. data/config/locales/id.yml +37 -0
  92. data/config/locales/it.yml +38 -0
  93. data/config/locales/ja.yml +36 -0
  94. data/config/locales/km.yml +37 -0
  95. data/config/locales/ko.yml +36 -0
  96. data/config/locales/ku.yml +37 -0
  97. data/config/locales/lt.yml +37 -0
  98. data/config/locales/lv.yml +39 -0
  99. data/config/locales/nb.yml +37 -0
  100. data/config/locales/ne.yml +37 -0
  101. data/config/locales/nl.yml +38 -0
  102. data/config/locales/pl.yml +39 -0
  103. data/config/locales/pt-BR.yml +34 -0
  104. data/config/locales/pt.yml +34 -0
  105. data/config/locales/ro.yml +37 -0
  106. data/config/locales/ru.yml +38 -0
  107. data/config/locales/sk.yml +31 -0
  108. data/config/locales/sl-SI.yml +31 -0
  109. data/config/locales/sv.yml +37 -0
  110. data/config/locales/th.yml +37 -0
  111. data/config/locales/tr.yml +38 -0
  112. data/config/locales/uk.yml +42 -0
  113. data/config/locales/vi.yml +35 -0
  114. data/config/locales/zh-CN.yml +38 -0
  115. data/config/locales/zh-TW.yml +40 -0
  116. data/lib/spree/emails/engine.rb +27 -3
  117. data/lib/spree/emails/previews/company_preview.rb +10 -0
  118. data/lib/spree/emails/previews/customer_preview.rb +4 -0
  119. data/lib/spree/emails/previews/digital_asset_preview.rb +20 -0
  120. metadata +107 -55
  121. data/app/helpers/spree/base_helper.rb +0 -45
  122. data/app/helpers/spree/digital_asset_helper.rb +0 -16
  123. data/app/helpers/spree/fulfillment_helper.rb +0 -12
  124. data/app/helpers/spree/mail_helper.rb +0 -10
  125. data/app/views/spree/company_mailer/invitation_email.html.erb +0 -8
  126. data/app/views/spree/company_mailer/invitation_email.text.erb +0 -11
  127. data/app/views/spree/customer_mailer/data_export_email.html.erb +0 -12
  128. data/app/views/spree/customer_mailer/data_export_email.text.erb +0 -9
  129. data/app/views/spree/customer_mailer/password_reset_email.html.erb +0 -10
  130. data/app/views/spree/customer_mailer/password_reset_email.text.erb +0 -13
  131. data/app/views/spree/digital_asset_mailer/files_ready_email.html.erb +0 -20
  132. data/app/views/spree/digital_asset_mailer/files_ready_email.text.erb +0 -10
  133. data/app/views/spree/fulfillment_mailer/fulfilled_email.html.erb +0 -22
  134. data/app/views/spree/fulfillment_mailer/fulfilled_email.text.erb +0 -18
  135. data/app/views/spree/newsletter_mailer/email_confirmation.html.erb +0 -12
  136. data/app/views/spree/newsletter_mailer/email_confirmation.text.erb +0 -9
  137. data/app/views/spree/order_group_mailer/confirm_email.html.erb +0 -38
  138. data/app/views/spree/order_group_mailer/confirm_email.text.erb +0 -26
  139. data/app/views/spree/order_group_mailer/store_owner_notification_email.html.erb +0 -14
  140. data/app/views/spree/order_group_mailer/store_owner_notification_email.text.erb +0 -11
  141. data/app/views/spree/order_mailer/cancel_email.html.erb +0 -12
  142. data/app/views/spree/order_mailer/cancel_email.text.erb +0 -13
  143. data/app/views/spree/order_mailer/confirm_email.html.erb +0 -12
  144. data/app/views/spree/order_mailer/confirm_email.text.erb +0 -13
  145. data/app/views/spree/order_mailer/payment_link_email.html.erb +0 -11
  146. data/app/views/spree/order_mailer/store_owner_notification_email.html.erb +0 -11
  147. data/app/views/spree/order_mailer/store_owner_notification_email.text.erb +0 -13
  148. data/app/views/spree/return_mailer/refunded_email.html.erb +0 -22
  149. data/app/views/spree/return_mailer/refunded_email.text.erb +0 -20
  150. data/app/views/spree/seller_mailer/approved_email.html.erb +0 -8
  151. data/app/views/spree/seller_mailer/approved_email.text.erb +0 -8
  152. data/app/views/spree/seller_mailer/rejected_email.html.erb +0 -6
  153. data/app/views/spree/seller_mailer/rejected_email.text.erb +0 -7
  154. data/app/views/spree/seller_mailer/suspended_email.html.erb +0 -6
  155. data/app/views/spree/seller_mailer/suspended_email.text.erb +0 -7
  156. data/app/views/spree/shared/_fulfillment_group_section.html.erb +0 -37
  157. data/app/views/spree/shared/_fulfillment_group_section.text.erb +0 -12
  158. data/app/views/spree/shared/_item_thumbnail_link.html.erb +0 -18
  159. data/app/views/spree/shared/_order_summary_section.html.erb +0 -20
  160. data/app/views/spree/shared/_po_number.text.erb +0 -6
  161. data/app/views/spree/shared/_purchase_totals.html.erb +0 -62
  162. data/app/views/spree/shared/_purchase_totals.text.erb +0 -24
  163. data/app/views/spree/shared/_purchased_items_table.html.erb +0 -41
  164. data/app/views/spree/shared/_purchased_items_table.text.erb +0 -28
  165. data/app/views/spree/shared/purchased_items_table/_line_item.html.erb +0 -24
  166. data/app/views/spree/shared/purchased_items_table/_subtotal.html.erb +0 -3
  167. data/app/views/spree/shared/purchased_items_table/_summary_row.html.erb +0 -14
  168. data/app/views/spree/shared/purchased_items_table/_total.html.erb +0 -4
  169. data/config/i18n-tasks.yml +0 -22
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ module Emails
3
+ # One real parcel of a purchase: what it carries, who sold it and what it
4
+ # cost to deliver.
5
+ class FulfillmentGroupSerializer
6
+ include Alba::Resource
7
+
8
+ attributes :name
9
+
10
+ attribute :display_cost do |group|
11
+ group.display_cost.to_s
12
+ end
13
+
14
+ attribute :seller_names do |group|
15
+ group.sellers.map(&:name)
16
+ end
17
+
18
+ many :manifest, key: :items, source: proc { manifest.select(&:line_item) },
19
+ resource: Spree::Emails::ParcelItemSerializer
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ module Emails
3
+ class FulfillmentSerializer < Spree::Api::V3::FulfillmentSerializer
4
+ typelize delivery_method_name: [:string, nullable: true]
5
+
6
+ attribute :delivery_method_name do |fulfillment|
7
+ fulfillment.delivery_method&.name
8
+ end
9
+
10
+ many :deliveries, resource: Spree::Emails::DeliverySerializer
11
+ one :delivery_method, resource: Spree::Emails::DeliveryMethodSerializer
12
+ one :stock_location, resource: Spree::Emails::StockLocationSerializer
13
+ many :delivery_rates, resource: Spree::Emails::DeliveryRateSerializer
14
+
15
+ many :manifest, key: :manifest_items, source: proc { manifest.select(&:line_item) },
16
+ resource: Spree::Emails::ParcelItemSerializer
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class GiftCardSerializer < Spree::Api::V3::GiftCardSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ module Emails
3
+ class LineItemSerializer < Spree::Api::V3::LineItemSerializer
4
+ include Spree::Emails::PurchasedItemAttributes
5
+
6
+ many :option_values, resource: Spree::Emails::OptionValueSerializer
7
+
8
+ typelize sku: [:string, nullable: true], display_amount: :string,
9
+ url: [:string, nullable: true], image_url: [:string, nullable: true]
10
+
11
+ attributes :sku
12
+
13
+ # Price times quantity, before discounts — what the line cost.
14
+ attribute :display_amount do |line_item|
15
+ line_item.display_amount.to_s
16
+ end
17
+
18
+ # The full-size image; emails show the thumbnail in `image_url`.
19
+ attributes :thumbnail_url, if: proc { false }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class MarketSerializer < Spree::Api::V3::MarketSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class OptionValueSerializer < Spree::Api::V3::OptionValueSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,60 @@
1
+ module Spree
2
+ module Emails
3
+ # A purchase that divided into several orders, as its confirmation reads
4
+ # it: every item, the parcels it ships in, and one set of totals.
5
+ class OrderGroupSerializer < Spree::Api::V3::OrderGroupSerializer
6
+ include Spree::Emails::PurchaseSummaryAttributes
7
+
8
+ typelize po_number: [:string, nullable: true], order_count: :number,
9
+ delivery_total: :string, display_delivery_total: :string,
10
+ additional_tax_total: :string, display_additional_tax_total: :string,
11
+ gift_card_total: :string, display_gift_card_total: :string
12
+
13
+ attributes :po_number
14
+
15
+ attribute :order_count do |group|
16
+ group.orders.size
17
+ end
18
+
19
+ attribute :display_delivery_total do |group|
20
+ group.display_delivery_total.to_s
21
+ end
22
+
23
+ attribute :delivery_total do |group|
24
+ group.delivery_total.to_s
25
+ end
26
+
27
+ attribute :additional_tax_total do |group|
28
+ group.additional_tax_total.to_s
29
+ end
30
+
31
+ attribute :display_additional_tax_total do |group|
32
+ group.display_additional_tax_total.to_s
33
+ end
34
+
35
+ attribute :gift_card_total do |group|
36
+ group.gift_card_total.to_s
37
+ end
38
+
39
+ attribute :display_gift_card_total do |group|
40
+ group.display_gift_card_total.to_s
41
+ end
42
+
43
+ # Every child order in full is what the webhook payload carries; the
44
+ # email reads the purchase-wide fields above instead.
45
+ many :orders, resource: proc { Spree.api.order_serializer }, if: proc { false }
46
+
47
+ many :fulfillment_groups, resource: Spree::Emails::FulfillmentGroupSerializer
48
+
49
+ # What no parcel carries — a download, an emailed gift card.
50
+ many :unfulfilled_items,
51
+ source: proc { unfulfilled_line_items },
52
+ resource: Spree::Emails::LineItemSerializer
53
+
54
+ # One delivery row for the whole purchase; the parcels break it down.
55
+ many :delivery_lines,
56
+ source: proc { delivery_total.zero? ? [] : [Spree::Emails::AmountLine.new(amount: delivery_total, currency: currency)] },
57
+ resource: Spree::Emails::AmountLineSerializer
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ module Emails
3
+ # An order as its emails read it: the Store API order plus what only an
4
+ # email shows — who to greet, the summary rows and line thumbnails.
5
+ class OrderSerializer < Spree::Api::V3::OrderSerializer
6
+ include Spree::Emails::PurchaseSummaryAttributes
7
+
8
+ many :order_promotions, key: :discounts, resource: Spree::Emails::AppliedPromotionSerializer
9
+ many :fees, resource: Spree::Emails::FeeSerializer
10
+ many :fulfillments, resource: Spree::Emails::FulfillmentSerializer
11
+ many :payments, resource: Spree::Emails::PaymentSerializer
12
+ one :gift_card, resource: Spree::Emails::GiftCardSerializer
13
+ one :market, resource: Spree::Emails::MarketSerializer
14
+
15
+ # One row per delivery rate, at its cost before discounts, so the rows
16
+ # add up to the total once a free-delivery promotion is listed.
17
+ many :delivery_lines,
18
+ source: proc {
19
+ Spree::Emails::AmountLine.group(fulfillments, currency: currency, amount: :cost, keep_zero: true,
20
+ label: ->(fulfillment) { fulfillment.selected_delivery_rate&.name })
21
+ },
22
+ resource: Spree::Emails::AmountLineSerializer
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ module Spree
2
+ module Emails
3
+ # One line as it travels in one parcel. A line packed from two warehouses
4
+ # rides in two parcels, so the quantity and amount are the parcel's share
5
+ # rather than the whole line's.
6
+ class ParcelItemSerializer < Spree::Emails::BaseSerializer
7
+ include Spree::Emails::PurchasedItemAttributes
8
+
9
+ attribute :id do |item|
10
+ item.line_item.prefixed_id
11
+ end
12
+
13
+ attribute :name do |item|
14
+ item.line_item.name
15
+ end
16
+
17
+ attributes :quantity
18
+
19
+ attribute :sku do |item|
20
+ item.line_item.sku
21
+ end
22
+
23
+ attribute :options_text do |item|
24
+ item.line_item.options_text
25
+ end
26
+
27
+ attribute :display_price do |item|
28
+ item.line_item.display_price.to_s
29
+ end
30
+
31
+ attribute :display_amount do |item|
32
+ Spree::Money.new(item.line_item.price * item.quantity, currency: item.line_item.currency).to_s
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class PaymentMethodSerializer < Spree::Api::V3::PaymentMethodSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Emails
3
+ class PaymentSerializer < Spree::Api::V3::PaymentSerializer
4
+ typelize source: ['Record<string, unknown>', nullable: true]
5
+
6
+ one :payment_method, resource: Spree::Emails::PaymentMethodSerializer
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ module Emails
3
+ # What an email shows for a whole purchase — who to greet, the items and
4
+ # the summary rows — for both an order and a multi-seller purchase. The
5
+ # `spree/shared/purchase_totals` partial reads these names from either.
6
+ module PurchaseSummaryAttributes
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ typelize customer_name: :string, display_total_minus_store_credits: :string
11
+
12
+ attribute :customer_name do |purchase|
13
+ purchase.name.presence || I18n.t('spree.customer')
14
+ end
15
+
16
+ attribute :display_total_minus_store_credits do |purchase|
17
+ purchase.display_total_minus_store_credits.to_s
18
+ end
19
+
20
+ many :line_items, key: :items, resource: Spree::Emails::LineItemSerializer
21
+ one :billing_address, resource: Spree::Emails::AddressSerializer
22
+ one :shipping_address, resource: Spree::Emails::AddressSerializer
23
+
24
+ many :promotion_discounts,
25
+ source: proc { Spree::Emails::AmountLine.group(discounts.promotion, currency: currency) },
26
+ resource: Spree::Emails::AmountLineSerializer
27
+
28
+ many :manual_discounts,
29
+ source: proc { Spree::Emails::AmountLine.group(discounts.manual, currency: currency) },
30
+ resource: Spree::Emails::AmountLineSerializer
31
+
32
+ # A multi-seller checkout apportions an order-level fee into one row
33
+ # per child, all under one label; summed back, it is the one charge the
34
+ # customer saw.
35
+ many :fee_lines,
36
+ source: proc { Spree::Emails::AmountLine.group(fees, currency: currency) },
37
+ resource: Spree::Emails::AmountLineSerializer
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ module Emails
3
+ # What an email shows for a purchased line besides its Store API fields: a
4
+ # link to the product on the storefront and a thumbnail. Shared by the line
5
+ # item and parcel item serializers.
6
+ module PurchasedItemAttributes
7
+ extend ActiveSupport::Concern
8
+ include Spree::ImagesHelper
9
+
10
+ included do
11
+ attribute :url do |item|
12
+ product = purchased_line_item(item).product
13
+ "#{params[:storefront_url]}/products/#{product.slug}" if product
14
+ end
15
+
16
+ attribute :image_url do |item|
17
+ spree_image_url(purchased_line_item(item).thumbnail, variant: :small)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def purchased_line_item(item)
24
+ item.is_a?(Spree::LineItem) ? item : item.line_item
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Emails
3
+ class ReturnSerializer < Spree::Api::V3::ReturnSerializer
4
+ typelize display_refunded_total: :string
5
+
6
+ # What was actually paid back — not the Store API's refund_total, which
7
+ # is the figure the return was expected to refund.
8
+ attribute :display_refunded_total do |return_record|
9
+ return_record.display_refunded_total.to_s
10
+ end
11
+
12
+ many :returned_items,
13
+ source: proc { return_line_items.filter_map(&:line_item).uniq },
14
+ resource: Spree::Emails::LineItemSerializer
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Emails
3
+ class StockLocationSerializer < Spree::Api::V3::StockLocationSerializer
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,79 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ # Builds what an email's template receives, for previewing a template
5
+ # and checking it before it is published: from the store's most recent
6
+ # matching record, or one the merchant picks. Values that would carry a
7
+ # token (reset, invitation, download links) are always placeholders.
8
+ class Base
9
+ # @return [String, nil] the kind of record the merchant can pick
10
+ # ("order", "order_group", "fulfillment", "return"), or nil when the
11
+ # email needs none
12
+ def self.record_type
13
+ nil
14
+ end
15
+
16
+ # @return [Array<String>] the permission keys a caller needs to see this
17
+ # sample, since it shows a real record's data
18
+ def self.required_permissions
19
+ record_type ? %w[read_orders] : []
20
+ end
21
+
22
+ attr_reader :store, :record_id
23
+
24
+ # @param store [Spree::Store]
25
+ # @param record_id [String, nil] a prefixed id of the record to preview with
26
+ def initialize(store:, record_id: nil)
27
+ @store = store
28
+ @record_id = record_id.presence
29
+ end
30
+
31
+ # @return [Hash] the template's variables, before `store` and `locale` are added
32
+ def variables
33
+ raise NotImplementedError
34
+ end
35
+
36
+ # @param limit [Integer]
37
+ # @return [Array<Spree::EmailTemplates::SampleRecord>] the store's latest
38
+ # records the email can be previewed with; empty when it needs none
39
+ def recent_records(limit: 5)
40
+ return [] unless self.class.record_type
41
+
42
+ records.limit(limit).map do |record|
43
+ Spree::EmailTemplates::SampleRecord.new(
44
+ id: record.prefixed_id, label: record.try(:number).presence || record.prefixed_id, created_at: record.created_at
45
+ )
46
+ end
47
+ end
48
+
49
+ # @return [String] the currency the email's amounts are in
50
+ def currency
51
+ (self.class.record_type && record.try(:currency)).presence || store.default_currency
52
+ end
53
+
54
+ protected
55
+
56
+ def record
57
+ @record ||= if record_id
58
+ records.find_by_prefix_id!(record_id)
59
+ else
60
+ records.first || raise(Spree::EmailTemplates::NoSampleRecord, I18n.t('spree.email_templates.no_sample_record', record: self.class.record_type.to_s.humanize.downcase))
61
+ end
62
+ end
63
+
64
+ # Every lookup goes through the store, so another store's record never answers.
65
+ def records
66
+ raise NotImplementedError
67
+ end
68
+
69
+ def data(object, serializer)
70
+ Spree::Emails::TemplateData.call(object, serializer, store: store, currency: currency)
71
+ end
72
+
73
+ def placeholder_url(path)
74
+ "#{store.storefront_url.to_s.chomp('/')}/#{path}?token=preview"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ class CompanyInvitation < Base
5
+ def self.required_permissions
6
+ %w[read_customers]
7
+ end
8
+
9
+ def variables
10
+ {
11
+ company: data(store.companies.order(created_at: :desc).first || Spree::Company.new(name: 'Acme Corporation', store: store),
12
+ Spree.api.company_serializer),
13
+ accept_url: placeholder_url('account/company-invitation')
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ class DataExport < Base
5
+ def variables
6
+ { download_url: placeholder_url('account/data-export'), expires_at: 7.days.from_now.iso8601 }
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ # The files-ready email. Lists the order's own downloads when it has any,
5
+ # always with placeholder links.
6
+ class Downloads < Order
7
+ def variables
8
+ {
9
+ order: data(record, Spree::Emails::OrderSerializer),
10
+ downloads: downloads,
11
+ resend: false
12
+ }
13
+ end
14
+
15
+ private
16
+
17
+ def downloads
18
+ links = record.digital_links.includes(digital_asset: { attachment_attachment: :blob }).to_a
19
+ return [{ filename: 'guide.pdf', url: placeholder_url('downloads'), expires_at: nil }] if links.empty?
20
+
21
+ links.map do |link|
22
+ { filename: link.filename.to_s, url: placeholder_url('downloads'), expires_at: link.expires_at&.iso8601 }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ class Fulfillment < Base
5
+ def self.required_permissions
6
+ %w[read_orders read_fulfillments]
7
+ end
8
+
9
+ def self.record_type
10
+ 'fulfillment'
11
+ end
12
+
13
+ def variables
14
+ {
15
+ fulfillment: data(record, Spree::Emails::FulfillmentSerializer),
16
+ order: data(record.order, Spree::Emails::OrderSerializer),
17
+ resend: false
18
+ }
19
+ end
20
+
21
+ def currency
22
+ record.order.currency
23
+ end
24
+
25
+ protected
26
+
27
+ def records
28
+ store.fulfillments.order(created_at: :desc)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ class NewsletterConfirmation < Base
5
+ def variables
6
+ { confirmation_url: placeholder_url('newsletter/confirm') }
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ # Order confirmation and cancellation.
5
+ class Order < Base
6
+ def self.record_type
7
+ 'order'
8
+ end
9
+
10
+ def variables
11
+ { order: data(record, Spree::Emails::OrderSerializer), resend: false }
12
+ end
13
+
14
+ protected
15
+
16
+ def records
17
+ store.orders.complete.order(completed_at: :desc)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ # The confirmation of a purchase that divided into several orders.
5
+ class OrderGroup < Base
6
+ def self.record_type
7
+ 'order_group'
8
+ end
9
+
10
+ def variables
11
+ { order_group: data(record, Spree::Emails::OrderGroupSerializer), resend: false }
12
+ end
13
+
14
+ protected
15
+
16
+ def records
17
+ store.order_groups.order(created_at: :desc)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ class PasswordReset < Base
5
+ def variables
6
+ {
7
+ customer: data(Spree.customer_class.new(first_name: 'Jane', last_name: 'Doe', email: 'jane@example.com'),
8
+ Spree.api.customer_serializer),
9
+ reset_url: placeholder_url('account/reset-password')
10
+ }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ class PaymentLink < Order
5
+ def variables
6
+ { order: data(record, Spree::Emails::OrderSerializer), payment_url: placeholder_url('checkout/payment') }
7
+ end
8
+
9
+ protected
10
+
11
+ def records
12
+ store.orders.order(created_at: :desc)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ module Spree
2
+ module Emails
3
+ module Samples
4
+ class Return < Base
5
+ def self.record_type
6
+ 'return'
7
+ end
8
+
9
+ def variables
10
+ {
11
+ return: data(record, Spree::Emails::ReturnSerializer),
12
+ order: data(record.order, Spree::Emails::OrderSerializer),
13
+ resend: false
14
+ }
15
+ end
16
+
17
+ def currency
18
+ record.order.currency
19
+ end
20
+
21
+ protected
22
+
23
+ def records
24
+ store.returns.order(created_at: :desc)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -20,7 +20,7 @@ module Spree
20
20
 
21
21
  def send_customer_confirmation(order, event)
22
22
  return if order.confirmation_delivered?
23
- return if event.payload['notify_customer'] == false
23
+ return if event.metadata['notify_customer'] == false
24
24
  return unless order.store.prefers_send_consumer_transactional_emails?
25
25
 
26
26
  OrderMailer.confirm_email(order.id).deliver_later
@@ -46,7 +46,7 @@ module Spree
46
46
  def send_cancel_email(event)
47
47
  order = find_order(event)
48
48
  return unless order
49
- return if event.payload['notify_customer'] == false
49
+ return if event.metadata['notify_customer'] == false
50
50
 
51
51
  store = order.store
52
52
  return unless store.prefers_send_consumer_transactional_emails?
@@ -27,7 +27,7 @@ module Spree
27
27
  # about the sale.
28
28
  def send_customer_confirmation(order_group, event)
29
29
  return if order_group.confirmation_delivered?
30
- return if event.payload['notify_customer'] == false
30
+ return if event.metadata['notify_customer'] == false
31
31
  return unless order_group.store.prefers_send_consumer_transactional_emails?
32
32
 
33
33
  OrderGroupMailer.confirm_email(order_group.id).deliver_later
@@ -10,6 +10,8 @@ module Spree
10
10
 
11
11
  store = return_record.store
12
12
  return unless store.prefers_send_consumer_transactional_emails?
13
+ # A return owed nothing closes without a refund, so there is none to announce.
14
+ return if return_record.refunded_total.zero?
13
15
 
14
16
  ReturnMailer.refunded_email(return_record.id).deliver_later
15
17
  end