ываыв
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
// server/src/lib/notifications/channels/email-channel.js
|
// server/src/lib/notifications/channels/email-channel.js
|
||||||
import { sendNotificationEmail } from '../../email.js'
|
import { sendNotificationEmail } from '../../email.js'
|
||||||
import {
|
import {
|
||||||
|
renderAdminOrderMessageEmail,
|
||||||
renderOrderCreatedEmail,
|
renderOrderCreatedEmail,
|
||||||
renderOrderStatusChangedEmail,
|
renderOrderStatusChangedEmail,
|
||||||
renderOrderMessageEmail,
|
renderOrderMessageEmail,
|
||||||
@@ -17,7 +18,7 @@ const templateRenderers = {
|
|||||||
'orderMessage:adminReply': renderOrderMessageEmail,
|
'orderMessage:adminReply': renderOrderMessageEmail,
|
||||||
'payment:statusChanged': renderPaymentStatusChangedEmail,
|
'payment:statusChanged': renderPaymentStatusChangedEmail,
|
||||||
'order:created:admin': renderAdminOrderCreatedEmail,
|
'order:created:admin': renderAdminOrderCreatedEmail,
|
||||||
'orderMessage:sent': renderOrderMessageEmail,
|
'orderMessage:sent': renderAdminOrderMessageEmail,
|
||||||
'review:created': renderAdminNewReviewEmail,
|
'review:created': renderAdminNewReviewEmail,
|
||||||
'auth:codeRequested': renderAuthCodeEmail,
|
'auth:codeRequested': renderAuthCodeEmail,
|
||||||
'order:deliveryFeeAdjusted': renderDeliveryFeeAdjustedEmail,
|
'order:deliveryFeeAdjusted': renderDeliveryFeeAdjustedEmail,
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { afterEach, describe, expect, it } from 'vitest'
|
||||||
|
import {
|
||||||
|
renderAdminOrderMessageEmail,
|
||||||
|
renderDeliveryFeeAdjustedEmail,
|
||||||
|
renderOrderCreatedEmail,
|
||||||
|
renderOrderMessageEmail,
|
||||||
|
renderOrderStatusChangedEmail,
|
||||||
|
renderPaymentStatusChangedEmail,
|
||||||
|
} from '../email-templates.js'
|
||||||
|
|
||||||
|
const originalClientPublicUrl = process.env.CLIENT_PUBLIC_URL
|
||||||
|
const orderId = 'order-123456789'
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (originalClientPublicUrl === undefined) {
|
||||||
|
delete process.env.CLIENT_PUBLIC_URL
|
||||||
|
return
|
||||||
|
}
|
||||||
|
process.env.CLIENT_PUBLIC_URL = originalClientPublicUrl
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('email templates', () => {
|
||||||
|
it('adds personal account order links to order emails', () => {
|
||||||
|
process.env.CLIENT_PUBLIC_URL = 'https://shop.example.com/'
|
||||||
|
const expectedUrl = `https://shop.example.com/me/orders/${orderId}`
|
||||||
|
|
||||||
|
const emails = [
|
||||||
|
renderOrderCreatedEmail({ orderId, totalCents: 120000, itemsCount: 2, deliveryType: 'pickup' }),
|
||||||
|
renderOrderStatusChangedEmail({ orderId, oldStatus: 'PENDING_PAYMENT', newStatus: 'PAID' }),
|
||||||
|
renderPaymentStatusChangedEmail({ orderId, paymentStatus: 'confirmed' }),
|
||||||
|
renderDeliveryFeeAdjustedEmail({ orderId, totalCents: 135000 }),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const email of emails) {
|
||||||
|
expect(email.html).toContain(`href="${expectedUrl}"`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds personal account messages link to order message emails', () => {
|
||||||
|
process.env.CLIENT_PUBLIC_URL = 'https://shop.example.com'
|
||||||
|
|
||||||
|
const email = renderOrderMessageEmail({ orderId, preview: 'Здравствуйте' })
|
||||||
|
|
||||||
|
expect(email.html).toContain('href="https://shop.example.com/me/messages"')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds admin orders link to admin order message emails', () => {
|
||||||
|
process.env.CLIENT_PUBLIC_URL = 'https://shop.example.com'
|
||||||
|
|
||||||
|
const email = renderAdminOrderMessageEmail({ orderId, preview: 'Нужна консультация' })
|
||||||
|
|
||||||
|
expect(email.html).toContain('href="https://shop.example.com/admin/orders"')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -14,6 +14,36 @@ function baseLayout(title, body) {
|
|||||||
</html>`
|
</html>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getClientPublicUrl() {
|
||||||
|
return (process.env.CLIENT_PUBLIC_URL || 'http://127.0.0.1:5173').replace(/\/$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildClientUrl(path) {
|
||||||
|
return `${getClientPublicUrl()}${path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderActionLink(url, label) {
|
||||||
|
return `
|
||||||
|
<p style="margin:20px 0;">
|
||||||
|
<a href="${url}" style="display:inline-block;background:#1f2937;color:#fff;text-decoration:none;padding:10px 16px;border-radius:6px;font-weight:600;">
|
||||||
|
${label}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOrderUrl(orderId) {
|
||||||
|
return buildClientUrl(`/me/orders/${encodeURIComponent(orderId)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMessagesUrl() {
|
||||||
|
return buildClientUrl('/me/messages')
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAdminOrdersUrl() {
|
||||||
|
return buildClientUrl('/admin/orders')
|
||||||
|
}
|
||||||
|
|
||||||
export function renderOrderCreatedEmail({ orderId, totalCents, itemsCount, deliveryType }) {
|
export function renderOrderCreatedEmail({ orderId, totalCents, itemsCount, deliveryType }) {
|
||||||
const total = (totalCents / 100).toLocaleString('ru-RU')
|
const total = (totalCents / 100).toLocaleString('ru-RU')
|
||||||
const nextAction =
|
const nextAction =
|
||||||
@@ -22,6 +52,7 @@ export function renderOrderCreatedEmail({ orderId, totalCents, itemsCount, deliv
|
|||||||
<p>Ваш заказ <b>#${orderId.slice(0, 8)}</b> успешно создан.</p>
|
<p>Ваш заказ <b>#${orderId.slice(0, 8)}</b> успешно создан.</p>
|
||||||
<p>Товаров: ${itemsCount} | Сумма: <b>${total} ₽</b></p>
|
<p>Товаров: ${itemsCount} | Сумма: <b>${total} ₽</b></p>
|
||||||
<p>${nextAction}</p>
|
<p>${nextAction}</p>
|
||||||
|
${renderActionLink(buildOrderUrl(orderId), 'Открыть заказ в личном кабинете')}
|
||||||
`
|
`
|
||||||
return { subject: 'Заказ создан', html: baseLayout('Заказ создан', body) }
|
return { subject: 'Заказ создан', html: baseLayout('Заказ создан', body) }
|
||||||
}
|
}
|
||||||
@@ -42,6 +73,7 @@ export function renderOrderStatusChangedEmail({ orderId, oldStatus, newStatus })
|
|||||||
const body = `
|
const body = `
|
||||||
<p>Статус заказа <b>#${orderId.slice(0, 8)}</b> изменён.</p>
|
<p>Статус заказа <b>#${orderId.slice(0, 8)}</b> изменён.</p>
|
||||||
<p><b>${oldLabel}</b> → <b>${newLabel}</b></p>
|
<p><b>${oldLabel}</b> → <b>${newLabel}</b></p>
|
||||||
|
${renderActionLink(buildOrderUrl(orderId), 'Открыть заказ в личном кабинете')}
|
||||||
`
|
`
|
||||||
return {
|
return {
|
||||||
subject: `Статус заказа изменён — ${newLabel}`,
|
subject: `Статус заказа изменён — ${newLabel}`,
|
||||||
@@ -57,6 +89,23 @@ export function renderOrderMessageEmail({ orderId, preview }) {
|
|||||||
${truncated}
|
${truncated}
|
||||||
</div>
|
</div>
|
||||||
<p>Ответьте в личном кабинете.</p>
|
<p>Ответьте в личном кабинете.</p>
|
||||||
|
${renderActionLink(buildMessagesUrl(), 'Открыть сообщения в личном кабинете')}
|
||||||
|
`
|
||||||
|
return {
|
||||||
|
subject: 'Новое сообщение к заказу',
|
||||||
|
html: baseLayout('Новое сообщение', body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderAdminOrderMessageEmail({ orderId, preview }) {
|
||||||
|
const truncated = preview.length > 200 ? preview.slice(0, 197) + '...' : preview
|
||||||
|
const body = `
|
||||||
|
<p>Новое сообщение к заказу <b>#${orderId.slice(0, 8)}</b>:</p>
|
||||||
|
<div style="background:#f0f0f0;padding:12px;border-radius:6px;margin:12px 0;">
|
||||||
|
${truncated}
|
||||||
|
</div>
|
||||||
|
<p>Ответьте в админ-панели.</p>
|
||||||
|
${renderActionLink(buildAdminOrdersUrl(), 'Открыть заказы в админ-панели')}
|
||||||
`
|
`
|
||||||
return {
|
return {
|
||||||
subject: 'Новое сообщение к заказу',
|
subject: 'Новое сообщение к заказу',
|
||||||
@@ -73,6 +122,7 @@ export function renderPaymentStatusChangedEmail({ orderId, paymentStatus }) {
|
|||||||
const label = statusLabels[paymentStatus] || paymentStatus
|
const label = statusLabels[paymentStatus] || paymentStatus
|
||||||
const body = `
|
const body = `
|
||||||
<p>Статус оплаты заказа <b>#${orderId.slice(0, 8)}</b>: <b>${label}</b>.</p>
|
<p>Статус оплаты заказа <b>#${orderId.slice(0, 8)}</b>: <b>${label}</b>.</p>
|
||||||
|
${renderActionLink(buildOrderUrl(orderId), 'Открыть заказ в личном кабинете')}
|
||||||
`
|
`
|
||||||
return {
|
return {
|
||||||
subject: `Оплата заказа — ${label}`,
|
subject: `Оплата заказа — ${label}`,
|
||||||
@@ -115,6 +165,7 @@ export function renderDeliveryFeeAdjustedEmail({ orderId, totalCents }) {
|
|||||||
<p>Стоимость доставки заказа <b>#${orderId.slice(0, 8)}</b> скорректирована.</p>
|
<p>Стоимость доставки заказа <b>#${orderId.slice(0, 8)}</b> скорректирована.</p>
|
||||||
<p>Новая сумма: <b>${total} ₽</b></p>
|
<p>Новая сумма: <b>${total} ₽</b></p>
|
||||||
<p>Ожидает оплаты. Проверьте статус заказа в личном кабинете.</p>
|
<p>Ожидает оплаты. Проверьте статус заказа в личном кабинете.</p>
|
||||||
|
${renderActionLink(buildOrderUrl(orderId), 'Открыть заказ в личном кабинете')}
|
||||||
`
|
`
|
||||||
return {
|
return {
|
||||||
subject: 'Стоимость доставки скорректирована',
|
subject: 'Стоимость доставки скорректирована',
|
||||||
|
|||||||
Reference in New Issue
Block a user