Digital Products¶
Digital products let you sell downloadable items like software, ebooks, music, or any file stored in the Umbraco Media Library. Merchello handles secure download links, expiry, download limits, and automatic order completion for digital-only purchases.
Invariant: All digital settings live in
ProductRoot.ExtendedDataunder constant keys -- do not add model properties for them. Digital products require a customer account (no guest checkout) and cannot use variant options (IsVariant = true); use add-on options withPriceAdjustment/CostAdjustment/SkuSuffixinstead.
How Digital Products Work¶
A digital product is a regular product root with IsDigitalProduct = true. All digital-specific settings are stored in ProductRoot.ExtendedData using constant keys accessed via ProductRootDigitalExtensions.cs -- there are no extra database columns. Service contract: IDigitalProductService.cs.
Key Constraints¶
- Customer account required -- digital products cannot be purchased via guest checkout. Customers must create an account.
- No variant options -- digital products cannot use variant options (
IsVariant = true). They can only use add-on options (IsVariant = false) with price/cost adjustments. - Digital-only invoices auto-complete -- when an invoice contains only digital products and payment succeeds, the order is automatically marked as complete (no fulfilment needed).
Delivery Methods¶
Each digital product has a delivery method that controls how download links are presented to the customer:
public enum DigitalDeliveryMethod
{
InstantDownload = 0, // Links shown on order confirmation page
EmailDelivered = 1 // Links sent via email only
}
- InstantDownload -- the customer sees download links immediately on the order confirmation page, and also receives them by email.
- EmailDelivered -- download links are only sent via email. Useful for license keys, time-sensitive content, or when you want controlled delivery.
Configuring a Digital Product¶
Digital settings are stored in ProductRoot.ExtendedData and accessed via extension methods from ProductRootDigitalExtensions:
using Merchello.Core.DigitalProducts.Extensions;
// Set delivery method
productRoot.SetDigitalDeliveryMethod(DigitalDeliveryMethod.InstantDownload);
// Attach files (Umbraco Media IDs)
productRoot.SetDigitalFileIds(["media-guid-1", "media-guid-2"]);
// Set download link expiry (0 = unlimited)
productRoot.SetDownloadLinkExpiryDays(30); // Links valid for 30 days
// Set max downloads per link (0 = unlimited)
productRoot.SetMaxDownloadsPerLink(5); // Each link can be downloaded 5 times
Reading Digital Settings¶
var method = productRoot.GetDigitalDeliveryMethod(); // DigitalDeliveryMethod
var fileIds = productRoot.GetDigitalFileIds(); // List<string> of media IDs
var expiryDays = productRoot.GetDownloadLinkExpiryDays(); // int (default: 30)
var maxDownloads = productRoot.GetMaxDownloadsPerLink(); // int (default: 0 = unlimited)
var hasFiles = productRoot.HasDigitalFiles(); // bool
ExtendedData Keys¶
The constant keys used in ProductRoot.ExtendedData:
| Key | Value | Default |
|---|---|---|
DigitalDeliveryMethod |
"InstantDownload" or "EmailDelivered" |
InstantDownload |
DigitalFileIds |
JSON array of Umbraco Media IDs | [] |
DownloadLinkExpiryDays |
Integer as string, 0 = unlimited |
30 |
MaxDownloadsPerLink |
Integer as string, 0 = unlimited |
0 |
Download Links¶
When a customer pays for a digital product, IDigitalProductService creates download links for each file. Each link has:
- A unique HMAC-signed token for security.
- An optional expiry date based on the product's
DownloadLinkExpiryDays. - An optional download limit based on
MaxDownloadsPerLink. - A download count that increments with each download.
Service API¶
public interface IDigitalProductService
{
// Create links for all digital products in an invoice (idempotent)
Task<CrudResult<List<DownloadLink>>> CreateDownloadLinksAsync(
CreateDownloadLinksParameters parameters, CancellationToken ct);
// Validate a download token
Task<CrudResult<DownloadLink>> ValidateDownloadTokenAsync(
ValidateDownloadTokenParameters parameters, CancellationToken ct);
// Record a download (increments count)
Task<CrudResult<bool>> RecordDownloadAsync(Guid downloadLinkId, CancellationToken ct);
// Get all downloads for a customer
Task<List<DownloadLink>> GetCustomerDownloadsAsync(
GetCustomerDownloadsParameters parameters, CancellationToken ct);
// Get downloads for a specific invoice
Task<List<DownloadLink>> GetInvoiceDownloadsAsync(Guid invoiceId, CancellationToken ct);
// Check if invoice is digital-only
Task<bool> IsDigitalOnlyInvoiceAsync(Guid invoiceId, CancellationToken ct);
// Regenerate links (invalidates old ones)
Task<CrudResult<List<DownloadLink>>> RegenerateDownloadLinksAsync(
RegenerateDownloadLinksParameters parameters, CancellationToken ct);
}
Note:
CreateDownloadLinksAsyncis idempotent -- if links already exist for an invoice, it returns the existing ones rather than creating duplicates.
Download Security¶
Downloads go through the DownloadsController at GET /api/merchello/downloads/{token}. Security is enforced at multiple layers:
- Authentication required -- the endpoint requires
[Authorize]. Only logged-in customers can download. - Customer ownership -- the token is validated against the requesting customer's ID, preventing one customer from using another's download link.
- HMAC-signed tokens -- download URLs use cryptographically signed tokens, not guessable IDs.
- Constant-time validation -- token comparison uses constant-time algorithms to prevent timing attacks.
- Rate limiting -- the endpoint uses
[EnableRateLimiting("downloads")]to prevent abuse. - Path traversal protection -- file paths are validated to stay within wwwroot.
- Expiry and download limits -- links are checked for time expiry and download count limits.
Customer Download Endpoints¶
The DownloadsController provides two endpoints for customers to view their downloads:
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/merchello/downloads/customer |
All downloads for current customer |
GET |
/api/merchello/downloads/invoice/{invoiceId} |
Downloads for a specific invoice |
Both require authentication and validate customer ownership. The invoice endpoint additionally verifies that the customer owns the invoice.
The response includes:
{
"id": "guid",
"fileName": "ebook.pdf",
"downloadUrl": "/api/merchello/downloads/{token}",
"expiresUtc": "2026-04-27T00:00:00Z",
"maxDownloads": 5,
"downloadCount": 2,
"remainingDownloads": 3,
"lastDownloadUtc": "2026-03-28T10:30:00Z",
"isExpired": false,
"isDownloadLimitReached": false
}
Automatic Order Completion¶
When a digital-only invoice (no physical products) receives a successful payment, the DigitalProductPaymentHandler notification handler automatically:
- Creates download links for all digital files in the invoice.
- Marks the invoice/order as complete (no shipping or fulfilment needed).
- Publishes a
DigitalProductDeliveredNotificationso you can hook in email delivery or other post-processing.
Supported File Types¶
The download controller maps file extensions to content types automatically:
| Extension | Content Type |
|---|---|
.pdf |
application/pdf |
.zip |
application/zip |
.mp3 |
audio/mpeg |
.mp4 |
video/mp4 |
.jpg/.jpeg |
image/jpeg |
.png |
image/png |
.epub |
application/epub+zip |
.mobi |
application/x-mobipocket-ebook |
.doc/.docx |
MS Word |
.xls/.xlsx |
MS Excel |
| Other | application/octet-stream |
Key Points¶
- Digital product settings live in
ProductRoot.ExtendedData-- no extra database tables needed for configuration. - Download links are stored in the
merchelloDownloadLinkstable. - Files are stored in Umbraco's Media Library and served through a secure controller.
- Link creation is idempotent; regeneration explicitly invalidates old links.
- The
DownloadUrlonDownloadLinkis built at runtime usingMerchelloSettings.WebsiteUrlto ensure correct URLs behind reverse proxies.