diff --git a/docs/docs/reference/core-plugins/email-plugin/index.md b/docs/docs/reference/core-plugins/email-plugin/index.md index b585099021..f1ba037257 100644 --- a/docs/docs/reference/core-plugins/email-plugin/index.md +++ b/docs/docs/reference/core-plugins/email-plugin/index.md @@ -108,7 +108,10 @@ Dynamic data such as the recipient's name or order items are specified using [Ha The following helper functions are available for use in email templates: -* `formatMoney`: Formats an amount of money (which are always stored as integers in Vendure) as a decimal, e.g. `123` => `1.23` +* `formatMoney`: Formats an amount of money (which are always stored as integers in Vendure) as a decimal, e.g. `123` => `1.23`. + * Also accepts two additional parameters (`currency` and `locale`), which will format according to the given currency and locale. For example: + * `{{ formatMoney 123 'USD' 'en-US' }}` => `$1.23` + * `{{ formatMoney 123 'EUR' 'de-DE' }}` => `1,23 €` * `formatDate`: Formats a Date value with the [dateformat](https://www.npmjs.com/package/dateformat) package. ## Extending the default email handlers diff --git a/packages/email-plugin/src/handlebars-mjml-generator.ts b/packages/email-plugin/src/handlebars-mjml-generator.ts index 99cb08a4c3..df42ef61e3 100644 --- a/packages/email-plugin/src/handlebars-mjml-generator.ts +++ b/packages/email-plugin/src/handlebars-mjml-generator.ts @@ -56,11 +56,24 @@ export class HandlebarsMjmlGenerator implements EmailGenerator { return dateFormat(date, format); }); - Handlebars.registerHelper('formatMoney', (amount?: number) => { - if (amount == null) { - return amount; - } - return (amount / 100).toFixed(2); - }); + Handlebars.registerHelper( + 'formatMoney', + (amount?: number, currencyCode?: string, locale?: string) => { + if (amount == null) { + return amount; + } + // Last parameter is a generic "options" object which is not used here. + // If it's supplied, it means the helper function did not receive the additional, optional parameters. + // See https://handlebarsjs.com/api-reference/helpers.html#the-options-parameter + if (!currencyCode || typeof currencyCode === 'object') { + return (amount / 100).toFixed(2); + } + // Same reasoning for `locale` as for `currencyCode` here. + return new Intl.NumberFormat(typeof locale === 'object' ? undefined : locale, { + style: 'currency', + currency: currencyCode, + }).format(amount / 100); + }, + ); } } diff --git a/packages/email-plugin/src/plugin.spec.ts b/packages/email-plugin/src/plugin.spec.ts index b8d7fc3ae2..c74174c14f 100644 --- a/packages/email-plugin/src/plugin.spec.ts +++ b/packages/email-plugin/src/plugin.spec.ts @@ -21,6 +21,7 @@ import { createReadStream, readFileSync } from 'fs'; import path from 'path'; import { Readable } from 'stream'; import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; + import { orderConfirmationHandler } from './default-email-handlers'; import { EmailProcessor } from './email-processor'; import { EmailSender } from './email-sender'; @@ -338,6 +339,8 @@ describe('EmailPlugin', () => { eventBus.publish(new MockEvent(ctx, true)); await pause(); expect(onSend.mock.calls[0][0].body).toContain('Price: 1.23'); + expect(onSend.mock.calls[0][0].body).toContain('Price: €1.23'); + expect(onSend.mock.calls[0][0].body).toContain('Price: £1.23'); }); }); @@ -658,7 +661,7 @@ describe('EmailPlugin', () => { await pause(); expect(testingLogger.warnSpy.mock.calls[0][0]).toContain( - 'Email has a large \'content\' attachment (64k). Consider using the \'path\' instead for improved performance.', + "Email has a large 'content' attachment (64k). Consider using the 'path' instead for improved performance.", ); }); }); @@ -881,8 +884,8 @@ describe('EmailPlugin', () => { return { type: 'testing', onSend: () => {}, - } - } + }; + }, }); const ctx = RequestContext.deserialize({ _channel: { code: DEFAULT_CHANNEL_CODE }, @@ -891,7 +894,7 @@ describe('EmailPlugin', () => { module!.get(EventBus).publish(new MockEvent(ctx, true)); await pause(); expect(module).toBeDefined(); - expect(typeof (module.get(EmailPlugin) as any).options.transport).toBe('function'); + expect(typeof module.get(EmailPlugin).options.transport).toBe('function'); }); it('Passes injector and context to transport function', async () => { diff --git a/packages/email-plugin/templates/order-confirmation/body.hbs b/packages/email-plugin/templates/order-confirmation/body.hbs index a30bed98cf..1f898a2617 100644 --- a/packages/email-plugin/templates/order-confirmation/body.hbs +++ b/packages/email-plugin/templates/order-confirmation/body.hbs @@ -61,7 +61,7 @@ Total Price - ${{ formatMoney order.total }} + ${{ formatMoney order.total order.currencyCode 'en' }} @@ -101,7 +101,7 @@ {{ quantity }} x {{ productVariant.name }} {{ productVariant.quantity }} - ${{ formatMoney discountedLinePriceWithTax }} + ${{ formatMoney discountedLinePriceWithTax ../order.currencyCode 'en' }} {{/each}} {{#each order.discounts }} @@ -109,22 +109,22 @@ {{ description }} - ${{ formatMoney amount }} + ${{ formatMoney amount ../order.currencyCode 'en' }} {{/each}} Sub-total: - ${{ formatMoney order.subTotalWithTax }} + ${{ formatMoney order.subTotalWithTax order.currencyCode 'en' }} {{#each shippingLines }} Shipping ({{ shippingMethod.name }}): - ${{ formatMoney priceWithTax }} + ${{ formatMoney priceWithTax ../order.currencyCode 'en' }} {{/each}} Total: - ${{ formatMoney order.totalWithTax }} + ${{ formatMoney order.totalWithTax order.currencyCode 'en' }} diff --git a/packages/email-plugin/test-templates/test-helpers/body.hbs b/packages/email-plugin/test-templates/test-helpers/body.hbs index 88dd1ac040..818324a4ae 100644 --- a/packages/email-plugin/test-templates/test-helpers/body.hbs +++ b/packages/email-plugin/test-templates/test-helpers/body.hbs @@ -3,6 +3,8 @@ Price: {{ formatMoney myPrice }} + Price: {{ formatMoney myPrice "EUR" }} + Price: {{ formatMoney myPrice "GBP" "en" }} Date: {{ formatDate myDate 'UTC:ddd mmm dd yyyy HH:MM:ss' }}