feat: add transactional emails for order confirmation and shipping
Plain text emails via Swoosh OrderNotifier module. Order confirmation triggered from Stripe webhook after payment, shipping notification from Printify shipment webhook with polling fallback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3e19887499
commit
0af8997623
15
PROGRESS.md
15
PROGRESS.md
@ -17,9 +17,10 @@
|
|||||||
- Admin credentials page with guided Stripe setup flow
|
- Admin credentials page with guided Stripe setup flow
|
||||||
- Encrypted settings for API keys and secrets
|
- Encrypted settings for API keys and secrets
|
||||||
- Search modal with keyboard shortcut
|
- Search modal with keyboard shortcut
|
||||||
|
- Transactional emails (order confirmation, shipping notification)
|
||||||
- Demo content polished and ready for production
|
- Demo content polished and ready for production
|
||||||
|
|
||||||
**Next up:** Transactional emails — order confirmation and shipping notifications (Tier 1, Roadmap #3)
|
**Next up:** Default content pages — terms, privacy, delivery policy (Tier 1, Roadmap #4)
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
@ -27,7 +28,7 @@
|
|||||||
|
|
||||||
1. ~~**Order management admin**~~ — ✅ Complete (02cdc81). Admin UI at `/admin/orders` with status filter tabs, streamed order table, and detail view showing items, totals, and shipping address.
|
1. ~~**Order management admin**~~ — ✅ Complete (02cdc81). Admin UI at `/admin/orders` with status filter tabs, streamed order table, and detail view showing items, totals, and shipping address.
|
||||||
2. ~~**Orders & fulfilment**~~ — ✅ Complete. Submit paid orders to Printify, track fulfilment status (submitted → processing → shipped → delivered), webhook-driven status updates with polling fallback, admin UI with submit/refresh actions.
|
2. ~~**Orders & fulfilment**~~ — ✅ Complete. Submit paid orders to Printify, track fulfilment status (submitted → processing → shipped → delivered), webhook-driven status updates with polling fallback, admin UI with submit/refresh actions.
|
||||||
3. **Transactional emails** — Order confirmation email on payment. Shipping notification with tracking link. Use Swoosh (already configured) with a simple HTML template.
|
3. ~~**Transactional emails**~~ — ✅ Complete. Plain text order confirmation (on payment via Stripe webhook) and shipping notification (on dispatch via Printify webhook + polling fallback). OrderNotifier module, 10 tests.
|
||||||
4. **Default content pages** — Static pages for terms of service, delivery & refunds policy, and privacy policy. Needed for legal compliance before taking real orders. Can be simple markdown-rendered pages initially, upgraded to editable via page editor later.
|
4. **Default content pages** — Static pages for terms of service, delivery & refunds policy, and privacy policy. Needed for legal compliance before taking real orders. Can be simple markdown-rendered pages initially, upgraded to editable via page editor later.
|
||||||
|
|
||||||
### Tier 2 — Production readiness (can deploy and run reliably)
|
### Tier 2 — Production readiness (can deploy and run reliably)
|
||||||
@ -172,7 +173,7 @@ See: [ROADMAP.md](ROADMAP.md) for design notes
|
|||||||
- CSSCache test startup crash fixed (handle_continue pattern)
|
- CSSCache test startup crash fixed (handle_continue pattern)
|
||||||
|
|
||||||
### Orders & Fulfilment
|
### Orders & Fulfilment
|
||||||
**Status:** Complete (checkout, admin, fulfilment). Transactional emails pending (Roadmap #3).
|
**Status:** Complete
|
||||||
|
|
||||||
- [x] Orders context with schemas (ff1bc48)
|
- [x] Orders context with schemas (ff1bc48)
|
||||||
- [x] Stripe Checkout integration with webhook handling
|
- [x] Stripe Checkout integration with webhook handling
|
||||||
@ -191,7 +192,12 @@ See: [ROADMAP.md](ROADMAP.md) for design notes
|
|||||||
- Stripe webhook auto-enqueues submission after payment confirmed
|
- Stripe webhook auto-enqueues submission after payment confirmed
|
||||||
- Admin UI: fulfilment badge column, fulfilment card with tracking, submit/refresh buttons
|
- Admin UI: fulfilment badge column, fulfilment card with tracking, submit/refresh buttons
|
||||||
- Mox provider mocking for test isolation, 33 new tests (555 total)
|
- Mox provider mocking for test isolation, 33 new tests (555 total)
|
||||||
- [ ] Transactional emails (Roadmap #3)
|
- [x] Transactional emails (Roadmap #3)
|
||||||
|
- OrderNotifier module with plain text emails via Swoosh
|
||||||
|
- Order confirmation sent from Stripe webhook after payment + address/email updates
|
||||||
|
- Shipping notification sent from Printify shipment webhook + polling fallback
|
||||||
|
- Guards for missing customer_email, graceful tracking info handling
|
||||||
|
- 10 tests (565 total)
|
||||||
|
|
||||||
See: [docs/plans/products-context.md](docs/plans/products-context.md) for schema design
|
See: [docs/plans/products-context.md](docs/plans/products-context.md) for schema design
|
||||||
|
|
||||||
@ -208,6 +214,7 @@ See: [docs/plans/page-builder.md](docs/plans/page-builder.md) for design
|
|||||||
|
|
||||||
| Feature | Commit | Notes |
|
| Feature | Commit | Notes |
|
||||||
|---------|--------|-------|
|
|---------|--------|-------|
|
||||||
|
| Transactional emails | — | Plain text order confirmation + shipping notification, 10 tests |
|
||||||
| Printify order submission & fulfilment | — | Submit, track, webhooks, polling, admin UI, 33 tests |
|
| Printify order submission & fulfilment | — | Submit, track, webhooks, polling, admin UI, 33 tests |
|
||||||
| Order management admin | 02cdc81 | List/detail views, status filters, 15 tests |
|
| Order management admin | 02cdc81 | List/detail views, status filters, 15 tests |
|
||||||
| Encrypted settings & Stripe setup | eede9bb | Guided setup flow, encrypted secrets, admin credentials page |
|
| Encrypted settings & Stripe setup | eede9bb | Guided setup flow, encrypted secrets, admin credentials page |
|
||||||
|
|||||||
@ -9,7 +9,7 @@ defmodule SimpleshopTheme.Orders do
|
|||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
alias SimpleshopTheme.Repo
|
alias SimpleshopTheme.Repo
|
||||||
alias SimpleshopTheme.Orders.{Order, OrderItem}
|
alias SimpleshopTheme.Orders.{Order, OrderItem, OrderNotifier}
|
||||||
alias SimpleshopTheme.Products
|
alias SimpleshopTheme.Products
|
||||||
alias SimpleshopTheme.Providers.Provider
|
alias SimpleshopTheme.Providers.Provider
|
||||||
|
|
||||||
@ -233,7 +233,13 @@ defmodule SimpleshopTheme.Orders do
|
|||||||
}
|
}
|
||||||
|> maybe_set_timestamp(order)
|
|> maybe_set_timestamp(order)
|
||||||
|
|
||||||
update_fulfilment(order, attrs)
|
with {:ok, updated_order} <- update_fulfilment(order, attrs) do
|
||||||
|
if attrs[:fulfilment_status] == "shipped" and order.fulfilment_status != "shipped" do
|
||||||
|
OrderNotifier.deliver_shipping_notification(updated_order)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, updated_order}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
130
lib/simpleshop_theme/orders/order_notifier.ex
Normal file
130
lib/simpleshop_theme/orders/order_notifier.ex
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
defmodule SimpleshopTheme.Orders.OrderNotifier do
|
||||||
|
@moduledoc """
|
||||||
|
Sends transactional emails for orders.
|
||||||
|
|
||||||
|
Order confirmation after payment, shipping notification when dispatched.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Swoosh.Email
|
||||||
|
|
||||||
|
alias SimpleshopTheme.Cart
|
||||||
|
alias SimpleshopTheme.Mailer
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Sends an order confirmation email after successful payment.
|
||||||
|
|
||||||
|
Skips silently if the order has no customer email.
|
||||||
|
"""
|
||||||
|
def deliver_order_confirmation(%{customer_email: nil}), do: {:ok, :no_email}
|
||||||
|
def deliver_order_confirmation(%{customer_email: ""}), do: {:ok, :no_email}
|
||||||
|
|
||||||
|
def deliver_order_confirmation(order) do
|
||||||
|
subject = "Order confirmed - #{order.order_number}"
|
||||||
|
|
||||||
|
body = """
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Thanks for your order!
|
||||||
|
|
||||||
|
Order: #{order.order_number}
|
||||||
|
|
||||||
|
#{format_items(order.items)}
|
||||||
|
Total: #{Cart.format_price(order.total)}
|
||||||
|
|
||||||
|
#{format_shipping_address(order.shipping_address)}
|
||||||
|
We'll send you another email when your order ships.
|
||||||
|
|
||||||
|
==============================
|
||||||
|
"""
|
||||||
|
|
||||||
|
deliver(order.customer_email, subject, body)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Sends a shipping notification with tracking info.
|
||||||
|
|
||||||
|
Skips silently if the order has no customer email.
|
||||||
|
"""
|
||||||
|
def deliver_shipping_notification(%{customer_email: nil}), do: {:ok, :no_email}
|
||||||
|
def deliver_shipping_notification(%{customer_email: ""}), do: {:ok, :no_email}
|
||||||
|
|
||||||
|
def deliver_shipping_notification(order) do
|
||||||
|
subject = "Your order has shipped - #{order.order_number}"
|
||||||
|
|
||||||
|
body = """
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Good news! Your order #{order.order_number} is on its way.
|
||||||
|
|
||||||
|
#{format_tracking(order)}
|
||||||
|
Thanks for shopping with us.
|
||||||
|
|
||||||
|
==============================
|
||||||
|
"""
|
||||||
|
|
||||||
|
deliver(order.customer_email, subject, body)
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Private ---
|
||||||
|
|
||||||
|
defp deliver(recipient, subject, body) do
|
||||||
|
email =
|
||||||
|
new()
|
||||||
|
|> to(recipient)
|
||||||
|
|> from({"SimpleshopTheme", "contact@example.com"})
|
||||||
|
|> subject(subject)
|
||||||
|
|> text_body(body)
|
||||||
|
|
||||||
|
case Mailer.deliver(email) do
|
||||||
|
{:ok, _metadata} = result ->
|
||||||
|
result
|
||||||
|
|
||||||
|
{:error, reason} = error ->
|
||||||
|
Logger.warning("Failed to send email to #{recipient}: #{inspect(reason)}")
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_items(items) when is_list(items) do
|
||||||
|
items
|
||||||
|
|> Enum.map_join("\n", fn item ->
|
||||||
|
price = Cart.format_price(item.unit_price * item.quantity)
|
||||||
|
" #{item.quantity}x #{item.product_name} (#{item.variant_title}) - #{price}"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_items(_), do: ""
|
||||||
|
|
||||||
|
defp format_shipping_address(address) when is_map(address) and map_size(address) > 0 do
|
||||||
|
lines =
|
||||||
|
[
|
||||||
|
address["name"],
|
||||||
|
address["line1"],
|
||||||
|
address["line2"],
|
||||||
|
[address["city"], address["postal_code"]] |> Enum.reject(&is_nil/1) |> Enum.join(" "),
|
||||||
|
address["state"],
|
||||||
|
address["country"]
|
||||||
|
]
|
||||||
|
|> Enum.reject(&(is_nil(&1) or &1 == ""))
|
||||||
|
|> Enum.map_join("\n", &" #{&1}")
|
||||||
|
|
||||||
|
"Shipping to:\n#{lines}\n\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_shipping_address(_), do: ""
|
||||||
|
|
||||||
|
defp format_tracking(order) do
|
||||||
|
cond do
|
||||||
|
order.tracking_url not in [nil, ""] and order.tracking_number not in [nil, ""] ->
|
||||||
|
"Tracking: #{order.tracking_number}\n#{order.tracking_url}\n\n"
|
||||||
|
|
||||||
|
order.tracking_number not in [nil, ""] ->
|
||||||
|
"Tracking: #{order.tracking_number}\n\n"
|
||||||
|
|
||||||
|
true ->
|
||||||
|
"Tracking details will follow once the carrier updates.\n\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -4,6 +4,7 @@ defmodule SimpleshopTheme.Webhooks do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
alias SimpleshopTheme.Orders
|
alias SimpleshopTheme.Orders
|
||||||
|
alias SimpleshopTheme.Orders.OrderNotifier
|
||||||
alias SimpleshopTheme.Products
|
alias SimpleshopTheme.Products
|
||||||
alias SimpleshopTheme.Sync.ProductSyncWorker
|
alias SimpleshopTheme.Sync.ProductSyncWorker
|
||||||
alias SimpleshopTheme.Webhooks.ProductDeleteWorker
|
alias SimpleshopTheme.Webhooks.ProductDeleteWorker
|
||||||
@ -44,14 +45,17 @@ defmodule SimpleshopTheme.Webhooks do
|
|||||||
def handle_printify_event("order:shipment:created", resource) do
|
def handle_printify_event("order:shipment:created", resource) do
|
||||||
shipment = extract_shipment(resource)
|
shipment = extract_shipment(resource)
|
||||||
|
|
||||||
with {:ok, order} <- find_order_from_resource(resource) do
|
with {:ok, order} <- find_order_from_resource(resource),
|
||||||
|
{:ok, updated_order} <-
|
||||||
Orders.update_fulfilment(order, %{
|
Orders.update_fulfilment(order, %{
|
||||||
fulfilment_status: "shipped",
|
fulfilment_status: "shipped",
|
||||||
provider_status: "shipped",
|
provider_status: "shipped",
|
||||||
tracking_number: shipment.tracking_number,
|
tracking_number: shipment.tracking_number,
|
||||||
tracking_url: shipment.tracking_url,
|
tracking_url: shipment.tracking_url,
|
||||||
shipped_at: DateTime.utc_now() |> DateTime.truncate(:second)
|
shipped_at: DateTime.utc_now() |> DateTime.truncate(:second)
|
||||||
})
|
}) do
|
||||||
|
OrderNotifier.deliver_shipping_notification(updated_order)
|
||||||
|
{:ok, updated_order}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ defmodule SimpleshopThemeWeb.StripeWebhookController do
|
|||||||
use SimpleshopThemeWeb, :controller
|
use SimpleshopThemeWeb, :controller
|
||||||
|
|
||||||
alias SimpleshopTheme.Orders
|
alias SimpleshopTheme.Orders
|
||||||
alias SimpleshopTheme.Orders.OrderSubmissionWorker
|
alias SimpleshopTheme.Orders.{OrderNotifier, OrderSubmissionWorker}
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@ -56,6 +56,9 @@ defmodule SimpleshopThemeWeb.StripeWebhookController do
|
|||||||
order
|
order
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Reload items for the email (update_order doesn't preload)
|
||||||
|
order = Orders.get_order(order.id)
|
||||||
|
|
||||||
# Broadcast to success page via PubSub
|
# Broadcast to success page via PubSub
|
||||||
Phoenix.PubSub.broadcast(
|
Phoenix.PubSub.broadcast(
|
||||||
SimpleshopTheme.PubSub,
|
SimpleshopTheme.PubSub,
|
||||||
@ -63,6 +66,8 @@ defmodule SimpleshopThemeWeb.StripeWebhookController do
|
|||||||
{:order_paid, order}
|
{:order_paid, order}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OrderNotifier.deliver_order_confirmation(order)
|
||||||
|
|
||||||
# Submit to fulfilment provider
|
# Submit to fulfilment provider
|
||||||
if order.shipping_address && order.shipping_address != %{} do
|
if order.shipping_address && order.shipping_address != %{} do
|
||||||
OrderSubmissionWorker.enqueue(order.id)
|
OrderSubmissionWorker.enqueue(order.id)
|
||||||
|
|||||||
161
test/simpleshop_theme/orders/order_notifier_test.exs
Normal file
161
test/simpleshop_theme/orders/order_notifier_test.exs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
defmodule SimpleshopTheme.Orders.OrderNotifierTest do
|
||||||
|
use SimpleshopTheme.DataCase, async: true
|
||||||
|
|
||||||
|
import Swoosh.TestAssertions
|
||||||
|
import SimpleshopTheme.OrdersFixtures
|
||||||
|
|
||||||
|
alias SimpleshopTheme.Orders
|
||||||
|
alias SimpleshopTheme.Orders.OrderNotifier
|
||||||
|
|
||||||
|
describe "deliver_order_confirmation/1" do
|
||||||
|
test "sends confirmation with order details" do
|
||||||
|
order =
|
||||||
|
order_fixture(%{
|
||||||
|
customer_email: "buyer@example.com",
|
||||||
|
payment_status: "paid",
|
||||||
|
shipping_address: %{
|
||||||
|
"name" => "Jane Doe",
|
||||||
|
"line1" => "42 Test Street",
|
||||||
|
"city" => "London",
|
||||||
|
"postal_code" => "SW1A 1AA",
|
||||||
|
"country" => "GB"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, _email} = OrderNotifier.deliver_order_confirmation(order)
|
||||||
|
|
||||||
|
assert_email_sent(fn email ->
|
||||||
|
assert email.to == [{"", "buyer@example.com"}]
|
||||||
|
assert email.subject =~ "Order confirmed"
|
||||||
|
assert email.subject =~ order.order_number
|
||||||
|
assert email.text_body =~ order.order_number
|
||||||
|
assert email.text_body =~ "Test product"
|
||||||
|
assert email.text_body =~ "Jane Doe"
|
||||||
|
assert email.text_body =~ "42 Test Street"
|
||||||
|
assert email.text_body =~ "London"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes item quantities and prices" do
|
||||||
|
order =
|
||||||
|
order_fixture(%{
|
||||||
|
customer_email: "buyer@example.com",
|
||||||
|
payment_status: "paid",
|
||||||
|
quantity: 2,
|
||||||
|
unit_price: 1500
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, _email} = OrderNotifier.deliver_order_confirmation(order)
|
||||||
|
|
||||||
|
assert_email_sent(fn email ->
|
||||||
|
assert email.text_body =~ "2x Test product"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes order total" do
|
||||||
|
order =
|
||||||
|
order_fixture(%{
|
||||||
|
customer_email: "buyer@example.com",
|
||||||
|
payment_status: "paid",
|
||||||
|
unit_price: 2500
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, _email} = OrderNotifier.deliver_order_confirmation(order)
|
||||||
|
|
||||||
|
assert_email_sent(fn email ->
|
||||||
|
assert email.text_body =~ "Total:"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips when customer_email is nil" do
|
||||||
|
order = order_fixture(%{customer_email: nil})
|
||||||
|
|
||||||
|
# Override to nil since fixture sets a default
|
||||||
|
{:ok, order} = Orders.update_order(order, %{customer_email: nil})
|
||||||
|
|
||||||
|
assert {:ok, :no_email} = OrderNotifier.deliver_order_confirmation(order)
|
||||||
|
assert_no_email_sent()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips when customer_email is empty string" do
|
||||||
|
order = order_fixture()
|
||||||
|
{:ok, order} = Orders.update_order(order, %{customer_email: ""})
|
||||||
|
|
||||||
|
assert {:ok, :no_email} = OrderNotifier.deliver_order_confirmation(order)
|
||||||
|
assert_no_email_sent()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles missing shipping address" do
|
||||||
|
order = order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})
|
||||||
|
|
||||||
|
assert {:ok, _email} = OrderNotifier.deliver_order_confirmation(order)
|
||||||
|
assert_email_sent(subject: "Order confirmed - #{order.order_number}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "deliver_shipping_notification/1" do
|
||||||
|
test "sends notification with tracking info" do
|
||||||
|
{order, _v, _p, _c} = submitted_order_fixture(%{customer_email: "buyer@example.com"})
|
||||||
|
|
||||||
|
{:ok, order} =
|
||||||
|
Orders.update_fulfilment(order, %{
|
||||||
|
fulfilment_status: "shipped",
|
||||||
|
tracking_number: "1Z999AA1",
|
||||||
|
tracking_url: "https://ups.com/track/1Z999AA1",
|
||||||
|
shipped_at: DateTime.utc_now() |> DateTime.truncate(:second)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, _email} = OrderNotifier.deliver_shipping_notification(order)
|
||||||
|
|
||||||
|
assert_email_sent(fn email ->
|
||||||
|
assert email.to == [{"", "buyer@example.com"}]
|
||||||
|
assert email.subject =~ "Your order has shipped"
|
||||||
|
assert email.subject =~ order.order_number
|
||||||
|
assert email.text_body =~ "1Z999AA1"
|
||||||
|
assert email.text_body =~ "https://ups.com/track/1Z999AA1"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles missing tracking info gracefully" do
|
||||||
|
{order, _v, _p, _c} = submitted_order_fixture(%{customer_email: "buyer@example.com"})
|
||||||
|
|
||||||
|
{:ok, order} =
|
||||||
|
Orders.update_fulfilment(order, %{
|
||||||
|
fulfilment_status: "shipped",
|
||||||
|
shipped_at: DateTime.utc_now() |> DateTime.truncate(:second)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, _email} = OrderNotifier.deliver_shipping_notification(order)
|
||||||
|
|
||||||
|
assert_email_sent(fn email ->
|
||||||
|
assert email.text_body =~ "Tracking details will follow"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes tracking number without URL" do
|
||||||
|
{order, _v, _p, _c} = submitted_order_fixture(%{customer_email: "buyer@example.com"})
|
||||||
|
|
||||||
|
{:ok, order} =
|
||||||
|
Orders.update_fulfilment(order, %{
|
||||||
|
fulfilment_status: "shipped",
|
||||||
|
tracking_number: "RM123456789GB",
|
||||||
|
shipped_at: DateTime.utc_now() |> DateTime.truncate(:second)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, _email} = OrderNotifier.deliver_shipping_notification(order)
|
||||||
|
|
||||||
|
assert_email_sent(fn email ->
|
||||||
|
assert email.text_body =~ "RM123456789GB"
|
||||||
|
assert not (email.text_body =~ "Tracking details will follow")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips when customer_email is nil" do
|
||||||
|
{order, _v, _p, _c} = submitted_order_fixture()
|
||||||
|
{:ok, order} = Orders.update_order(order, %{customer_email: nil})
|
||||||
|
|
||||||
|
assert {:ok, :no_email} = OrderNotifier.deliver_shipping_notification(order)
|
||||||
|
assert_no_email_sent()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user