Browse Source

feat(email-plugin): Multiple currency support in formatMoney helper (#2531)

Serge Morel 2 years ago
parent
commit
ccf17fbdc7

+ 4 - 1
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

+ 19 - 6
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);
+            },
+        );
     }
 }

+ 7 - 4
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 () => {

+ 6 - 6
packages/email-plugin/templates/order-confirmation/body.hbs

@@ -61,7 +61,7 @@
     </mj-column>
     <mj-column>
         <mj-text css-class="callout-large"><strong>Total Price</strong></mj-text>
-        <mj-text css-class="callout-small">${{ formatMoney order.total }}</mj-text>
+        <mj-text css-class="callout-small">${{ formatMoney order.total order.currencyCode 'en' }}</mj-text>
     </mj-column>
 </mj-section>
 
@@ -101,7 +101,7 @@
                     </td>
                     <td>{{ quantity }} x {{ productVariant.name }}</td>
                     <td>{{ productVariant.quantity }}</td>
-                    <td>${{ formatMoney discountedLinePriceWithTax }}</td>
+                    <td>${{ formatMoney discountedLinePriceWithTax ../order.currencyCode 'en' }}</td>
                 </tr>
             {{/each}}
             {{#each order.discounts }}
@@ -109,22 +109,22 @@
                     <td colspan="3">
                         {{ description }}
                     </td>
-                    <td>${{ formatMoney amount }}</td>
+                    <td>${{ formatMoney amount ../order.currencyCode 'en' }}</td>
                 </tr>
             {{/each}}
             <tr class="order-row">
                 <td colspan="3">Sub-total:</td>
-                <td>${{ formatMoney order.subTotalWithTax }}</td>
+                <td>${{ formatMoney order.subTotalWithTax order.currencyCode 'en' }}</td>
             </tr>
             {{#each shippingLines }}
             <tr class="order-row">
                 <td colspan="3">Shipping ({{ shippingMethod.name }}):</td>
-                <td>${{ formatMoney priceWithTax }}</td>
+                <td>${{ formatMoney priceWithTax ../order.currencyCode 'en' }}</td>
             </tr>
             {{/each}}
             <tr class="order-row total-row">
                 <td colspan="3">Total:</td>
-                <td>${{ formatMoney order.totalWithTax }}</td>
+                <td>${{ formatMoney order.totalWithTax order.currencyCode 'en' }}</td>
             </tr>
         </mj-table>
     </mj-column>

+ 2 - 0
packages/email-plugin/test-templates/test-helpers/body.hbs

@@ -3,6 +3,8 @@
 <mj-section>
     <mj-column>
         <mj-text>Price: {{ formatMoney myPrice }}</mj-text>
+        <mj-text>Price: {{ formatMoney myPrice "EUR" }}</mj-text>
+        <mj-text>Price: {{ formatMoney myPrice "GBP" "en" }}</mj-text>
         <mj-text>Date: {{ formatDate myDate 'UTC:ddd mmm dd yyyy HH:MM:ss' }}</mj-text>
     </mj-column>
 </mj-section>