Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions astro.sidebar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -728,10 +728,12 @@ export function generateSidebar() {
{ label: 'Overview', link: '/dropins/order/containers/' },
{ label: 'CreateReturn', link: '/dropins/order/containers/create-return/' },
{ label: 'CustomerDetails', link: '/dropins/order/containers/customer-details/' },
{ label: 'InvoiceDetails', link: '/dropins/order/containers/invoice-details/' },
{ label: 'OrderCancelForm', link: '/dropins/order/containers/order-cancel-form/' },
{ label: 'OrderComments', link: '/dropins/order/containers/order-comments/' },
{ label: 'OrderCostSummary', link: '/dropins/order/containers/order-cost-summary/' },
{ label: 'OrderHeader', link: '/dropins/order/containers/order-header/' },
{ label: 'OrderInvoices', link: '/dropins/order/containers/order-invoices/' },
{ label: 'OrderProductList', link: '/dropins/order/containers/order-product-list/' },
{ label: 'OrderReturns', link: '/dropins/order/containers/order-returns/' },
{ label: 'OrderSearch', link: '/dropins/order/containers/order-search/' },
Expand Down
Binary file added public/images/dropins/order/invoice-details.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/dropins/order/order-invoices.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/content/docs/dropins/order/containers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ Containers are pre-built UI components that combine functionality, state managem
| --------- | ----------- |
| [CreateReturn](/dropins/order/containers/create-return/) | Learn about the `CreateReturn` container. |
| [CustomerDetails](/dropins/order/containers/customer-details/) | Learn about the `CustomerDetails` container. |
| [InvoiceDetails](/dropins/order/containers/invoice-details/) | Display one invoice within an order and download a PDF. |
| [OrderCancelForm](/dropins/order/containers/order-cancel-form/) | Learn about the `OrderCancelForm` container. |
| [OrderComments](/dropins/order/containers/order-comments/) | Learn about the `OrderComments` container. |
| [OrderCostSummary](/dropins/order/containers/order-cost-summary/) | Learn about the `OrderCostSummary` container. |
| [OrderHeader](/dropins/order/containers/order-header/) | *Enrichment needed - add description to `_dropin-enrichments/order/containers.json`* |
| [OrderInvoices](/dropins/order/containers/order-invoices/) | List invoices associated with an authenticated customer's order. |
| [OrderProductList](/dropins/order/containers/order-product-list/) | Learn about the `OrderProductList` container. |
| [OrderReturns](/dropins/order/containers/order-returns/) | Learn about the `OrderReturns` container. |
| [OrderSearch](/dropins/order/containers/order-search/) | Learn about the `OrderSearch` container. |
Expand Down
154 changes: 154 additions & 0 deletions src/content/docs/dropins/order/containers/invoice-details.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
title: InvoiceDetails container
description: Display an invoice for a customer's order and download a PDF with optional host-provided branding.
---

import OptionsTable from '@components/OptionsTable.astro';
import Aside from '@components/Aside.astro';
import Diagram from '@components/Diagram.astro';

The `InvoiceDetails` container displays one invoice associated with an order. It shows the invoice and order numbers, order date, payment method, billing and shipping addresses, products, invoiced quantities, and invoice totals.

<Diagram caption="InvoiceDetails container">
![An invoice details view with addresses, invoiced products, totals, and a Download PDF
button.](@images/dropins/order/invoice-details.png)
</Diagram>

<Aside type="note">
Use a release of `@dropins/storefront-order` that exports `InvoiceDetails` and supports the PDF
options described here. Invoice retrieval uses the authenticated customer's orders; an invoice
reference alone does not provide access to another customer's invoice.
</Aside>

## Configurations

The `InvoiceDetails` container provides the following configuration options:

<div className="invoice-details-options">
<OptionsTable
compact
options={[
['Options', 'Type', 'Req?', 'Description'],
['invoiceRef', 'string', 'Yes', 'The invoice number or ID to find within the current order.'],
['className', 'string', 'No', 'Adds a CSS class to the container wrapper.'],
[
'orderData',
'OrderDataModel',
'No',
'Supplies the initial order context. The container uses its number to fetch invoices and also listens for order/data events.',
],
[
'routeInvoicesList',
'() => string',
'No',
'Returns the destination for the Back to Order Details link. Use your order details or invoice-list route.',
],
[
'onDownloadPdf',
'() => void',
'No',
'Replaces the built-in PDF download. Omit this callback to use browser-generated PDFs.',
],
[
'getInvoicePdfBranding',
'() => InvoicePdfBranding\n| undefined\n| Promise<InvoicePdfBranding | undefined>',
'No',
'Supplies branding to the built-in PDF generator. This callback is not used when onDownloadPdf replaces the download.',
],
]}
/>
</div>

<style>{`
.invoice-details-options td:nth-child(2) {
white-space: pre-line;
}
`}</style>

## Order context and states

Provide `orderData` or receive an [`order/data`](/dropins/order/events/#orderdata-emits-and-listens) event from the existing Order initializer. The container fetches invoices for that order and matches `invoiceRef` against the invoice number or ID.

The container shows a loader during retrieval, a not-found alert if the invoice is absent from the order, and a separate error alert if retrieval fails.

## Example

This example reads both references from the URL, loads the order, and uses the built-in PDF download. The host application must authenticate the customer before fetching the order. Replace the paths with your storefront routes.

```javascript
import '../../scripts/initializers/order.js';
import { getOrderDetailsById } from '@dropins/storefront-order/api.js';
import { InvoiceDetails } from '@dropins/storefront-order/containers/InvoiceDetails.js';
import { render as orderRenderer } from '@dropins/storefront-order/render.js';

export default async function decorate(block) {
const { searchParams } = new URL(window.location.href);
const orderRef = searchParams.get('orderRef');
const invoiceRef = searchParams.get('invoiceRef');
if (!orderRef || !invoiceRef) {
throw new Error('Order and invoice references are required.');
}

const orderData = await getOrderDetailsById({
orderId: orderRef,
queryType: 'orderData',
});

await orderRenderer.render(InvoiceDetails, {
orderData,
invoiceRef,
routeInvoicesList: () => {
const query = new URLSearchParams({ orderRef });
return `/order-details?${query}`;
},
})(block);
}
```

## PDF download and branding

The built-in download creates a portrait A4 PDF in the browser and loads the renderer when the customer requests it. The filename is `invoice-<invoice-number>.pdf`, with unsupported filename characters replaced by hyphens. The container does not store PDFs on the server.

To add branding, supply `getInvoicePdfBranding` in the render options. Return an object with any of these fields:

<OptionsTable
compact
options={[
['Options', 'Type', 'Req?', 'Description'],
[
'logoDataUrl',
'string',
'No',
'A raster logo encoded as an image data URL, such as image/png or image/jpeg.',
],
['storeName', 'string', 'No', 'The store name to display in the PDF header.'],
['addressLines', 'string[]', 'No', 'Store address lines to display in the PDF header.'],
]}
/>

Add the callback to the render options in the previous example:

```javascript
await orderRenderer.render(InvoiceDetails, {
orderData,
invoiceRef,
getInvoicePdfBranding: () => ({
storeName: 'Example Store',
addressLines: ['100 Main Street', 'Austin, TX 78701'],
}),
})(block);
```

This branding is supplied by the host application. The renderer does not fetch the PDF logo or address configured in Adobe Commerce Admin. If your host loads an image from a different origin to create a data URL, that origin must allow the image request.

To use a backend document service instead, supply `onDownloadPdf` and implement the download in that callback. Do not pass an empty callback: it disables the built-in download.

## Current data limitations

- The current invoice query does not retrieve invoice status.
- The detail view and PDF calculate each line subtotal as sale price multiplied by invoiced quantity. The invoice's aggregate subtotal and grand total come from the backend; computed line amounts can differ from stored row totals for partial invoices because of rounding.
- Discounts appear separately in invoice totals. Item-level discount data is available in the model, but is not displayed as an additional line discount column.
- The PDF does not estimate item tax from order prices. It shows zero in the Tax column when aggregate invoice tax is zero, and an unavailable marker when aggregate tax is nonzero but item tax is unavailable. Aggregate tax appears in totals.
- The UI supports the [Order dictionary](/dropins/order/dictionary/). Built-in PDF headings use fixed English labels, and the default download uses the renderer's `en-US` locale.

For a list of invoices within the order, see [`OrderInvoices`](/dropins/order/containers/order-invoices/).
86 changes: 86 additions & 0 deletions src/content/docs/dropins/order/containers/order-invoices.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
title: OrderInvoices container
description: Display invoices for a customer's order and link each invoice to its details page.
---

import OptionsTable from '@components/OptionsTable.astro';
import Aside from '@components/Aside.astro';
import Diagram from '@components/Diagram.astro';

The `OrderInvoices` container displays invoices associated with one order. Each entry shows the invoice number, total amount, and invoiced item count. The item count is the sum of invoiced quantities, not the number of product lines.

<Diagram caption="OrderInvoices container">
![An order invoice list showing an invoice number, total amount, and invoiced item
count.](@images/dropins/order/order-invoices.png)
</Diagram>

<Aside type="note">
Use a release of `@dropins/storefront-order` that exports `OrderInvoices`. Invoice retrieval uses
the authenticated customer's orders. This container does not provide a cross-order invoice search
or a guest invoice lookup.
</Aside>

## Configurations

The `OrderInvoices` container provides the following configuration options:

<OptionsTable
compact
options={[
['Options', 'Type', 'Req?', 'Description'],
['className', 'string', 'No', 'Adds a CSS class to the container wrapper.'],
[
'orderData',
'OrderDataModel',
'No',
'Supplies the initial order context. The container uses its number to fetch invoices and also listens for order/data events.',
],
[
'routeInvoiceDetails',
'(invoice: OrderInvoiceModel, orderNumber: string) => string',
'No',
'Returns the URL for an invoice details page. Receives the selected invoice and its order number.',
],
]}
/>

## Order context and states

Provide `orderData` or load the order through the existing Order initializer so that the container receives an [`order/data`](/dropins/order/events/#orderdata-emits-and-listens) event. Passing an order does not bypass customer authorization.

The container shows a loader while the order or its invoices load. It shows an empty state when the order has no invoices and a separate error alert when invoice retrieval fails.

## Example

This example uses the order number in the page's `orderRef` query parameter. The host application must initialize the Order drop-in and authenticate the customer before fetching the order. Replace the example paths with your storefront routes.

```javascript
import '../../scripts/initializers/order.js';
import { getOrderDetailsById } from '@dropins/storefront-order/api.js';
import { OrderInvoices } from '@dropins/storefront-order/containers/OrderInvoices.js';
import { render as orderRenderer } from '@dropins/storefront-order/render.js';

export default async function decorate(block) {
const { searchParams } = new URL(window.location.href);
const orderRef = searchParams.get('orderRef');
if (!orderRef) throw new Error('An order reference is required.');

const orderData = await getOrderDetailsById({
orderId: orderRef,
queryType: 'orderData',
});

await orderRenderer.render(OrderInvoices, {
orderData,
routeInvoiceDetails: (invoice, orderNumber) => {
const query = new URLSearchParams({
orderRef: orderNumber,
invoiceRef: invoice.number,
});
return `/invoice-details?${query}`;
},
})(block);
}
```

Use [`InvoiceDetails`](/dropins/order/containers/invoice-details/) on the destination page. For programmatic invoice retrieval, see [`getOrderInvoices`](/dropins/order/functions/#getorderinvoices).
67 changes: 67 additions & 0 deletions src/content/docs/dropins/order/dictionary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,78 @@ You only need to include the keys you want to change. For multi-language support

## Default keys and values

### Invoice labels

Invoice UI strings use `Order.OrderInvoices` and `Order.InvoiceDetails`. Customize list headings, item counts, navigation labels, download-button text, column labels, totals, and error messages through the existing `langDefinitions` configuration.

```javascript
const invoiceLabels = {
Order: {
OrderInvoices: {
title: 'Order invoices',
emptyState: 'No invoices for this order.',
},
InvoiceDetails: {
backToOrderDetails: 'Back to your order',
downloadPdf: 'Download invoice',
},
},
};
```

Merge these overrides into the locale entry in `langDefinitions`. They change the storefront interface, not the fixed English headings inside the built-in PDF. See [PDF download and branding](/dropins/order/containers/invoice-details/#pdf-download-and-branding).

Below are the default English (`en_US`) strings provided by the **Order** drop-in:

```json title="en_US.json"
{
"Order": {
"OrderInvoices": {
"emptyState": "No invoices.",
"title": "Invoices",
"invoiceNumber": "Invoice #",
"itemsCount": {
"singular": "{{count}} item",
"plural": "{{count}} items"
},
"loadError": {
"heading": "Unable to load invoices",
"description": "Please try again later."
}
},
"InvoiceDetails": {
"backToOrderDetails": "Back to Order Details",
"downloadPdf": "Download PDF",
"invoiceNumberLabel": "Invoice number",
"orderNumber": "Order number",
"orderDate": "Order date",
"paymentMethod": "Payment method",
"status": "Status",
"billingAddress": "Billing address",
"shippingAddress": "Shipping address",
"items": {
"productName": "Product Name",
"sku": "SKU",
"price": "Unit price",
"qtyInvoiced": "Qty Invoiced",
"subtotal": "Subtotal"
},
"totals": {
"subtotal": "Subtotal",
"discounts": "Discounts",
"tax": "Tax",
"shipping": "Shipping & Handling",
"grandTotal": "Total"
},
"notFound": {
"heading": "Invoice not found",
"description": "We couldn't find this invoice, or it doesn't belong to your account."
},
"loadError": {
"heading": "Unable to load invoice",
"description": "Please try again later."
}
},
"CreateReturn": {
"headerText": "Return items",
"downloadableCount": "Files",
Expand Down
2 changes: 2 additions & 0 deletions src/content/docs/dropins/order/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ events.on('companyContext/changed', (payload) => {

Emitted when order data is loaded or updated. This includes order details, items, shipping information, and payment status.

[OrderInvoices](/dropins/order/containers/order-invoices/) and [InvoiceDetails](/dropins/order/containers/invoice-details/) listen eagerly to this event, so they can use the most recent order payload when mounted. They fetch invoices using the order number and refetch when it changes. `InvoiceDetails` also refetches when its invoice reference changes. Fetching invoices does not emit a separate invoice event or add the invoice response to the `order/data` payload.

#### Event payload

```typescript
Expand Down
Loading
Loading