A self-hosted BTCPay Server plugin for selling digital media and generated software licenses from one branded storefront. Customers can combine products in one cart, sign in with a one-time email code, pay through BTCPay's JavaScript checkout modal, and return to a private purchase library.
Version 1.7.2 safely imports legacy orders and licenses whose indexed values exceed PostgreSQL column limits while preserving their complete payloads and exact lookup behavior. Remote-file validation now blocks Azure WireServer, Equinix Metal metadata, and IPv4-embedded aliases of cloud-local endpoints, and the legacy purchase route enforces the same bounded email policy as customer sign-in.
Version 1.7.1 ensures the indexed record schema is created by BTCPay's normal startup migration pipeline, safely reconciles records created during a downgrade without overwriting newer database state, and serializes legacy imports across server nodes. It also bounds and rate-limits passwordless verification, avoids writes for terminal challenges, rate-limits the legacy license invoice route, marks secret-bearing customer responses private and non-cacheable, and disables proxy routing for DNS-pinned remote-file connections.
Version 1.7.0 hardens public delivery and prepares the plugin for high-volume stores. Legacy order pages now require an opaque access token or the authenticated buyer session, remote-file connections pin the DNS answer validated at socket-connect time, login-code email has independent global IP, store, and recipient limits, orders and licenses use an indexed and paginated plugin database with automatic migration from prior store-setting collections, and license activation uses optimistic concurrency so simultaneous requests cannot exceed the configured limit.
Version 1.6.4 introduces a dedicated MakePay Digital Products identity across the BTCPay sidebar, marketplace listing, and documentation-site favicon family. The compact document-and-download mark is vector-native, inherits BTCPay navigation states, and remains legible from 16-pixel browser icons through 512-pixel listing artwork.
Version 1.6.3 adds a dedicated, responsive admin order workspace for complete checkout, customer, invoice, line-item, protected-delivery, usage, and license-fulfillment history. It keeps deleted-product snapshots useful, returns revocation actions to the detail page, and deliberately excludes delivery tokens, license keys, security hashes, IP hashes, and storage locations from the Razor model.
Version 1.6.2 adds server-side order search, product and payment-status filters, selectable page sizes, stable numbered pagination, responsive controls, and full result counts while preserving global dashboard totals and historical deleted-product labels.
Version 1.6.1 redesigns Store settings as a focused BTCPay-native dashboard with accessible section tabs, health summaries, responsive grouped forms, validation-aware navigation, smooth in-page interactions, and persistent save actions.
Version 1.6.0 unifies Products and License Keys in one BTCPay-native administration view, gives the fullscreen editor adaptive BTCPay light/dark styling and polished uploads, adds background cart feedback without page reloads, stabilizes carousel height, and adds a native sidebar icon.
Version 1.5.0 registers a native BTCPay App identity so server administrators can use BTCPay's Policies domain mapping with clean /downloads routes across the complete public storefront and protected-delivery surface.
Version 1.4.1 adds a per-store favicon URL and managed local favicon upload. The configured icon is emitted consistently across the shop, product, cart, sign-in, payment, confirmation, purchase-library, protected-delivery, and legacy license pages; an empty setting emits no custom favicon tag.
Version 1.4 adds consent-aware Google Tag Manager and direct Google Analytics 4 commerce analytics to the media storefront introduced in version 1.3. The existing file-download and license data model remains compatible:
- File download — protected delivery for archives, source files, templates, and other downloadable assets.
- PDF / ebook — optional public sample and a book-style browser powered by the bundled PDF.js viewer.
- Music & audio — optional public demo, protected playback, and configurable stream/download delivery.
- Video content — optional trailer, protected playback, and configurable stream/download delivery for courses, tutorials, and footage.
- Photos & art — public preview galleries with derived, downscaled, optionally watermarked preview assets while originals remain behind purchase access.
- Software license — generated serials, activation limits, signed verification APIs, and lifecycle management.
Existing per-store settings, products, orders, issued licenses, and API credentials migrate in place. Version 1.7.0 moves dynamic order, checkout, and license records from store-setting JSON collections into indexed database rows on first access, then clears the migrated legacy collections. Older products and fulfillment snapshots that do not contain a media type continue as file downloads.
- A professional BTCPay-native product editor with media-type selection, delivery controls, metadata, cover art, preview/demo management, fulfillment configuration, and publication state.
- An editable storefront with responsive product cards, product detail pages, type-aware previews, custom categories, hero slides, logo, favicon, colors, typography, and page content.
- Empty categories are hidden. Existing custom categories and explicit product selections continue to work.
- Passwordless customer access using encrypted, one-time, expiring email codes and encrypted store-scoped sessions.
- A private customer library with protected downloads and streams, recoverable license keys, activation state, and checkout history.
- A server-created invoice opened through BTCPay's official
/modal/btcpay.jsintegration, with payment-state polling and product unlock. - Enforced MakePay.io attribution and backlink in the public footer.
The storefront can use a branded hostname after the domain and BTCPay reverse proxy are configured. Native clean-domain routing then provides:
https://shop.example.com/downloads
For an official Docker deployment:
-
Configure an A/AAAA record to the BTCPay server, or a CNAME to its public hostname, and wait for DNS to resolve.
-
Confirm ports 80 and 443 reach BTCPay.
-
As the server administrator, set
BTCPAY_ADDITIONAL_HOSTSto the complete comma-separated list, retaining all existing additional hostnames, then rerun setup:# Replace both placeholders. Run as root from the btcpayserver-docker directory. export BTCPAY_ADDITIONAL_HOSTS="<your-new-host>,<all-existing-additional-hosts>" . ./btcpay-setup.sh -i
A CNAME alone is insufficient: the receiving reverse proxy must accept the hostname and provide a valid TLS certificate. Verify DNS before adding the hostname, because an unresolved or incorrect additional hostname can prevent Let's Encrypt renewal for every configured hostname, including the primary BTCPay domain.
BTCPAY_ADDITIONAL_HOSTS aliases the entire BTCPay Server, not only this store. Other BTCPay pages and store-scoped public routes remain reachable through that hostname, so it must not be treated as domain-to-store isolation.
After DNS, HTTPS, and BTCPAY_ADDITIONAL_HOSTS are working:
- Open Store → Integrations → Digital Products and explicitly create a Digital Products app identity if the store does not already have one. The plugin never creates or changes AppData during a public request.
- As a BTCPay server administrator, open Server settings → Policies → Domain mapping.
- Add the exact normalized ASCII hostname (punycode for an internationalized domain; no scheme, port, whitespace, or trailing dot) and select the Digital Products app for this store.
- Verify
https://your-host/downloads, its cart and sign-in flows, and a protected delivery link. If BTCPay uses a root path such as/btcpay, verifyhttps://your-host/btcpay/downloadsinstead.
BTCPay's native domain constraint supplies the mapped AppData to each clean-domain request. The plugin derives the store exclusively from AppData.StoreDataId; a query string, form field, or route value named storeId cannot select another store. The mapped surface includes storefront, product details, previews, runtime assets, cart, passwordless sign-in, checkout/payment state, confirmation, library, streams, and protected file delivery. Generated invoice, redirect, and email links use the mapped hostname and the configured BTCPay root path. Legacy /stores/<storeId>/downloads routes remain available, while safe GET/HEAD requests are canonicalized to the mapped URL. Tor visits to a legacy route remain on the current .onion origin, including invoice, email delivery, and passwordless-library return links.
Hostname assignment remains server-admin-only because /downloads is global on a mapped hostname. Keep one active Digital Products app identity per store. BTCPay does not reject duplicate domain rows: for the same exact hostname (case-insensitive), the first row in the global Policies list wins, even when a later row points to this plugin. Remove or reorder duplicates, and store the hostname in the same normalized ASCII/punycode form the browser sends because BTCPay's native constraint does not trim or normalize policy values. If the apex domain already hosts another website, its proxy or CDN must route /downloads and every nested path to BTCPay; a dedicated shop. subdomain is recommended. DNS, certificates, reverse-proxy rules, and BTCPAY_ADDITIONAL_HOSTS cannot be configured from inside the plugin.
See the official BTCPay documentation for Docker additional hosts, mapping domains to BTCPay Apps, and external reverse proxy and TLS configuration.
- Choose one provider per store: Google Tag Manager or direct Google Analytics 4. Provider IDs are validated and normalized before save so the same events are not reported twice by the plugin.
- GA4 commerce events cover
view_item_list,select_item,view_item,add_to_cart,remove_from_cart,view_cart,begin_checkout,add_payment_info, andpurchase. Purchase uses best-effort browser deduplication plus a stable one-way transaction ID so GA4 can deduplicate repeated delivery. - Every commerce payload contains normalized currency, value, and item data. Buyer emails, license keys, delivery tokens, checkout access tokens, and raw order/checkout identifiers are never included. Purchase transaction IDs are stable one-way analytics identifiers.
- Optional consent prevents Google scripts and event collection before acceptance. Rejected visitors have no private replay queue, and the preference can be changed from the persistent Analytics preferences control. Revoking consent reloads into a Google-script-free state. Browser Do Not Track can also be enforced.
- Direct GA4 disables automatic page views and sends only a sanitized origin-plus-path location. Dynamic checkout/order capability segments are masked, referrers receive the same treatment, and the local
dataLayerfollows the identical contract. When Do Not Track is enabled, collection is disabled in Google and local data-layer modes.
GTM containers are merchant-controlled JavaScript and can still read the current browser URL. Protected checkout, receipt, and sign-in routes can contain access-token or email parameters. Configure GTM tags to trigger from the plugin's page_view event and map the supplied page_location and page_path values. Do not use an automatic All Pages page-view trigger or GTM's built-in Page URL variable on these routes.
- Original products can use local protected uploads, private S3-compatible objects, or authenticated custom source URLs.
- Local originals are scoped to the owning store and product. Public preview files are stored separately under a store/product/preview boundary.
- Product fulfillment data is snapshotted at checkout so later catalog edits do not redirect an existing purchase to a different source.
- Files stream through BTCPay Server; origin paths, credentials, and private object URLs are not exposed to the customer.
- Single-range HTTP delivery supports seekable audio/video playback without consuming an additional download for every range request.
- Cryptographically random delivery tokens, SHA-256 token hashes, encrypted recoverable tokens, expiration, download limits, revocation, and optional first-IP locking.
- Remote-origin SSRF protection with socket-time public-address validation, DNS-answer pinning, disabled redirects, and validated custom authentication headers.
- Consolidated purchase delivery and passwordless login through the BTCPay store SMTP configuration with editable HTML templates.
Public previews are intentionally public. A photo watermark discourages casual reuse but is not a substitute for keeping the original private. Likewise, tokenized audio/video streaming controls access and supports expiry and revocation, but browser playback is not DRM and cannot prevent a determined customer from recording content they can play.
Preview and demo uploads are limited to 95 MB so requests remain below the common 100 MB BTCPay reverse-proxy limit. Large master files should use a private S3-compatible object or protected custom origin. PDF previews use the bundled PDF.js assets from the same BTCPay origin; see THIRD_PARTY_NOTICES.md.
- Configurable serial formats with
{A:n},{N:n},{X:n},{HEX:n}, and{YEAR}tokens. - Encrypted recoverable license keys plus normalized hashes for lookup.
- Manual issuance and invoice-based fulfillment with activation count and validity controls.
- Verify, activate, deactivate, and heartbeat endpoints at
/api/v1/stores/{storeId}/licenses/{action}. - Configurable custom
X-headers, HMAC-SHA256 request/response signatures, constant-time comparison, timestamp windows, nonce replay protection, rate limiting, activation limits, and bounded audit history.
- BTCPay Server 2.3.5 or newer.
- .NET 8 SDK to build.
- BTCPay store email settings if email delivery or passwordless customer access is enabled.
git submodule update --init --recursive
dotnet test tests/BTCPayServer.Plugins.MakePay.DigitalProducts.Tests/BTCPayServer.Plugins.MakePay.DigitalProducts.Tests.csproj -c Release
dotnet publish src/BTCPayServer.Plugins.MakePay.DigitalProducts/BTCPayServer.Plugins.MakePay.DigitalProducts.csproj -c ReleaseInstall the published plugin folder in the BTCPay Server plugin directory, restart BTCPay Server, then open Store → Integrations → Digital Products.
- Back up the BTCPay data directory.
- If present, remove the standalone
BTCPayServer.Plugins.MakePay.LicenseManagerplugin folder. - Install
BTCPayServer.Plugins.MakePay.DigitalProductsversion 1.7.0 or newer. - Restart BTCPay Server. The plugin creates its database schema automatically. Existing dynamic records migrate on first use, existing products default to File download, and empty media categories stay hidden until products are added.
Use private S3 buckets and dedicated read-only credentials. The optional IP lock is useful against casual link sharing but can inconvenience mobile users whose network address changes. Download and license delivery defaults to settled/confirmed invoices; enabling delivery at Processing accepts additional payment risk.
Created by MakePay.io — accept 90+ currencies in a decentralized way in BTCPay Server.