digital-products
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDigital Products
数字产品
Overview
概述
Selling digital products — ebooks, software licenses, templates, music, courses — requires secure delivery after payment, download limits to prevent unauthorized sharing, and expiring access links. Every major platform either has this built in (WooCommerce) or has a mature app that handles it (Shopify, BigCommerce). Use the platform's native solution first; only build custom delivery infrastructure if your use case (complex license key management, subscription-gated content libraries) exceeds what apps offer.
销售数字产品——电子书、软件许可证、模板、音乐、课程——需要在付款后进行安全交付,设置下载限制以防止未经授权的共享,并提供过期的访问链接。各大主流平台要么内置了该功能(如WooCommerce),要么有成熟的应用来处理(如Shopify、BigCommerce)。优先使用平台的原生解决方案;只有当你的使用场景(复杂的许可证密钥管理、订阅 gated 内容库)超出应用提供的功能时,才需要构建自定义交付基础设施。
When to Use This Skill
何时使用该技能
- When adding downloadable products (PDFs, software, audio, templates) to an existing store
- When implementing a license key delivery system for software products
- When building a subscription that gates access to a digital content library
- When replacing publicly accessible download URLs with secure, expiring signed URLs
- 为现有店铺添加可下载产品(PDF、软件、音频、模板)时
- 为软件产品实现许可证密钥交付系统时
- 构建订阅专属的数字内容库访问权限控制时
- 将公开可访问的下载URL替换为安全、过期的签名URL时
Core Instructions
核心操作指南
Step 1: Determine platform and choose the right tool
步骤1:确定平台并选择合适工具
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Sky Pilot or Fileflare | Sky Pilot handles files, license keys, streaming video, and download limits; Fileflare is simpler for basic file downloads |
| WooCommerce | WooCommerce Downloadable Products (built-in) | WooCommerce has native digital product support including download limits, expiry, and secure token URLs |
| BigCommerce | Downloadable Digital Products (built-in) or Fileflare | BigCommerce handles file attachment and download delivery natively |
| Custom / Headless | Build delivery with S3 presigned URLs | Required when the platform has no native digital product support |
| 平台 | 推荐工具 | 原因 |
|---|---|---|
| Shopify | Sky Pilot 或 Fileflare | Sky Pilot 支持文件管理、许可证密钥、视频流及下载限制;Fileflare 更适用于基础文件下载场景 |
| WooCommerce | WooCommerce Downloadable Products(内置) | WooCommerce 原生支持数字产品,包括下载限制、过期设置及安全令牌URL |
| BigCommerce | Downloadable Digital Products(内置)或 Fileflare | BigCommerce 原生支持文件附件与下载交付 |
| 自定义/无头架构 | 基于S3预签名URL构建交付系统 | 当平台无原生数字产品支持时需使用该方案 |
Step 2: Platform-specific setup
步骤2:平台专属设置
Shopify
Shopify
Shopify does not have native digital product support — you need an app. Sky Pilot is the most feature-complete option.
Setting up Sky Pilot:
- Install Sky Pilot from the Shopify App Store
- Go to Sky Pilot → Files → Upload your digital file (PDF, ZIP, etc.)
- Go to Sky Pilot → Products and link the uploaded file to a specific product or variant
- Configure delivery settings:
- Download limit: set to 3–5 for standard products, unlimited for subscriptions
- Link expiry: 48–72 hours is standard; customers can request a new link from their account
- Automatic delivery: enabled by default — customers receive a download email immediately after payment
- Customize the delivery email under Sky Pilot → Settings → Email template
For license key products:
- In Sky Pilot, go to License Keys → Import and upload a CSV of your license keys
- Link the license key pool to the product
- Sky Pilot automatically assigns one key per purchase and includes it in the delivery email
For subscription-gated content (Sky Pilot + Recharge):
- Sky Pilot integrates with Recharge — customers with an active subscription automatically gain access; access is revoked when the subscription cancels
Shopify 无原生数字产品支持——你需要使用应用。Sky Pilot 是功能最全面的选项。
Sky Pilot 设置流程:
- 从 Shopify App Store 安装 Sky Pilot
- 进入 Sky Pilot → Files → Upload 上传你的数字文件(PDF、ZIP等)
- 进入 Sky Pilot → Products,将上传的文件关联至特定产品或变体
- 配置交付设置:
- 下载限制:标准产品设置为3–5次,订阅产品设为无限制
- 链接过期时间:标准为48–72小时;客户可从账户中申请新链接
- 自动交付:默认启用——客户付款后立即收到下载邮件
- 在 Sky Pilot → Settings → Email template 下自定义交付邮件
针对许可证密钥产品:
- 在 Sky Pilot 中,进入 License Keys → Import 上传许可证密钥的CSV文件
- 将许可证密钥池关联至产品
- Sky Pilot 会自动为每笔订单分配一个密钥,并将其包含在交付邮件中
针对订阅 gated 内容(Sky Pilot + Recharge):
- Sky Pilot 与 Recharge 集成——订阅处于活跃状态的客户自动获得访问权限;订阅取消后访问权限将被收回
WooCommerce
WooCommerce
WooCommerce has digital product delivery built in — no extra plugin required for basic use.
Setting up a downloadable product:
- Go to WooCommerce → Products → Add Product
- Check Downloadable (and optionally Virtual to skip shipping)
- Under Product Data → Downloadable Files:
- Click Add File and upload the file or enter a URL
- File name: the name shown to customers in their account
- Configure access settings:
- Download limit: enter a number (e.g., ) or leave blank for unlimited
5 - Download expiry: number of days after purchase (e.g., ), or leave blank for no expiry
365
- Download limit: enter a number (e.g.,
- WooCommerce generates a secure, token-based download URL for each purchase automatically
Forcing customer account for downloads:
- Go to WooCommerce → Settings → Accounts & Privacy
- Enable Grant access to downloadable products after payment and Require login to download
For license key products:
- Install License Manager for WooCommerce (free plugin)
- Go to License Manager → Add License Keys and bulk import your keys
- Link the license key pool to a product
- The plugin delivers keys in the order confirmation email and the customer's account page
WooCommerce 内置数字产品交付功能——基础使用无需额外插件。
设置可下载产品:
- 进入 WooCommerce → Products → Add Product
- 勾选 Downloadable(可选择勾选 Virtual 以跳过物流设置)
- 在 Product Data → Downloadable Files 下:
- 点击 Add File 上传文件或输入URL
- 文件名:显示给客户的文件名称(在其账户中)
- 配置访问设置:
- 下载限制:输入数字(如),留空则表示无限制
5 - 下载过期时间:购买后的天数(如),留空则表示永不过期
365
- 下载限制:输入数字(如
- WooCommerce 会自动为每笔订单生成安全的基于令牌的下载URL
强制客户登录以下载:
- 进入 WooCommerce → Settings → Accounts & Privacy
- 启用 付款后授予可下载产品访问权限 和 需登录才能下载
针对许可证密钥产品:
- 安装 License Manager for WooCommerce(免费插件)
- 进入 License Manager → Add License Keys 批量导入密钥
- 将许可证密钥池关联至产品
- 插件会在订单确认邮件和客户账户页面中交付密钥
BigCommerce
BigCommerce
Setting up digital delivery (built-in):
- Go to Products → Add Product
- Under Files, upload your digital file (BigCommerce supports files up to 512 MB)
- Set Max downloads to limit how many times the file can be downloaded per order
- BigCommerce sends an automatic download email after payment with a secure link
For more advanced delivery:
- Install SendOwl or Fileflare from the BigCommerce App Marketplace
- These apps add license key management, download analytics, and streaming video delivery
设置数字交付(内置功能):
- 进入 Products → Add Product
- 在 Files 下上传数字文件(BigCommerce 支持最大512 MB的文件)
- 设置 Max downloads 以限制每笔订单的文件下载次数
- BigCommerce 会在付款后自动发送包含安全链接的下载邮件
如需更高级的交付功能:
- 从 BigCommerce App Marketplace 安装 SendOwl 或 Fileflare
- 这些应用可添加许可证密钥管理、下载分析及视频流交付功能
Custom / Headless
自定义/无头架构
For headless storefronts, implement secure file delivery using S3 presigned URLs — never expose the raw S3 key:
typescript
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3 = new S3Client({ region: process.env.AWS_REGION });
// Generate a short-lived presigned URL for each download attempt
export async function generateDownloadUrl(orderId: string, digitalProductId: string) {
const access = await db.orderDigitalAccess.findUnique({
where: { orderId_digitalProductId: { orderId, digitalProductId } },
include: { digitalProduct: true },
});
if (!access) throw new Error('No access record found');
if (access.expiresAt && access.expiresAt < new Date()) throw new Error('Download access has expired');
if (access.maxDownloads && access.downloadCount >= access.maxDownloads) throw new Error('Download limit reached');
// Atomically increment download count
await db.orderDigitalAccess.update({ where: { id: access.id }, data: { downloadCount: { increment: 1 } } });
// 60-second presigned URL — short enough to prevent sharing, long enough for the redirect
const command = new GetObjectCommand({
Bucket: process.env.DIGITAL_PRODUCTS_BUCKET,
Key: access.digitalProduct.fileStorageKey,
ResponseContentDisposition: `attachment; filename="${access.digitalProduct.fileName}"`,
});
return getSignedUrl(s3, command, { expiresIn: 60 });
}
// Provision access after payment — call this from your payment webhook
export async function provisionDigitalAccess(orderId: string) {
const order = await db.orders.findUnique({
where: { id: orderId },
include: { items: { include: { variant: { include: { digitalProduct: true } } } } },
});
for (const item of order.items.filter(i => i.variant.digitalProduct)) {
const dp = item.variant.digitalProduct;
await db.orderDigitalAccess.upsert({
where: { orderId_digitalProductId: { orderId, digitalProductId: dp.id } },
create: {
orderId, digitalProductId: dp.id, downloadCount: 0,
maxDownloads: dp.downloadLimit,
expiresAt: dp.accessDurationDays ? new Date(Date.now() + dp.accessDurationDays * 86400000) : null,
},
update: {}, // Idempotent — webhooks can fire multiple times
});
}
}
// License key pool management
export async function assignLicenseKey(orderId: string, productId: string) {
return db.$transaction(async tx => {
const license = await tx.digitalProductLicenses.findFirst({
where: { productId, status: 'available' },
});
if (!license) throw new Error(`No available license keys for product ${productId}`);
await tx.digitalProductLicenses.update({
where: { id: license.id },
data: { status: 'sold', orderId, soldAt: new Date() },
});
return license.licenseKey;
});
}对于无头店铺,使用S3预签名URL实现安全文件交付——切勿暴露原始S3密钥:
typescript
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3 = new S3Client({ region: process.env.AWS_REGION });
// Generate a short-lived presigned URL for each download attempt
export async function generateDownloadUrl(orderId: string, digitalProductId: string) {
const access = await db.orderDigitalAccess.findUnique({
where: { orderId_digitalProductId: { orderId, digitalProductId } },
include: { digitalProduct: true },
});
if (!access) throw new Error('No access record found');
if (access.expiresAt && access.expiresAt < new Date()) throw new Error('Download access has expired');
if (access.maxDownloads && access.downloadCount >= access.maxDownloads) throw new Error('Download limit reached');
// Atomically increment download count
await db.orderDigitalAccess.update({ where: { id: access.id }, data: { downloadCount: { increment: 1 } } });
// 60-second presigned URL — short enough to prevent sharing, long enough for the redirect
const command = new GetObjectCommand({
Bucket: process.env.DIGITAL_PRODUCTS_BUCKET,
Key: access.digitalProduct.fileStorageKey,
ResponseContentDisposition: `attachment; filename="${access.digitalProduct.fileName}"`,
});
return getSignedUrl(s3, command, { expiresIn: 60 });
}
// Provision access after payment — call this from your payment webhook
export async function provisionDigitalAccess(orderId: string) {
const order = await db.orders.findUnique({
where: { id: orderId },
include: { items: { include: { variant: { include: { digitalProduct: true } } } } },
});
for (const item of order.items.filter(i => i.variant.digitalProduct)) {
const dp = item.variant.digitalProduct;
await db.orderDigitalAccess.upsert({
where: { orderId_digitalProductId: { orderId, digitalProductId: dp.id } },
create: {
orderId, digitalProductId: dp.id, downloadCount: 0,
maxDownloads: dp.downloadLimit,
expiresAt: dp.accessDurationDays ? new Date(Date.now() + dp.accessDurationDays * 86400000) : null,
},
update: {}, // Idempotent — webhooks can fire multiple times
});
}
}
// License key pool management
export async function assignLicenseKey(orderId: string, productId: string) {
return db.$transaction(async tx => {
const license = await tx.digitalProductLicenses.findFirst({
where: { productId, status: 'available' },
});
if (!license) throw new Error(`No available license keys for product ${productId}`);
await tx.digitalProductLicenses.update({
where: { id: license.id },
data: { status: 'sold', orderId, soldAt: new Date() },
});
return license.licenseKey;
});
}Step 3: Configure post-purchase delivery email
步骤3:配置购买后交付邮件
All platforms send an automatic delivery email — customize it to be clear and professional:
Content to include:
- Order confirmation number
- Product name and description
- Download link (or license key, displayed prominently)
- Download limit and expiry date if applicable
- Instructions for how to access/install the product
- Support contact if they have trouble
Shopify (Sky Pilot): Customize under Sky Pilot → Settings → Email Template
WooCommerce: Customize under WooCommerce → Settings → Emails → Customer Processing Order (includes download links automatically)
BigCommerce: Customize under Marketing → Transactional Emails → Order Status Notification
所有平台都会发送自动交付邮件——需将其自定义为清晰专业的样式:
需包含的内容:
- 订单确认编号
- 产品名称与描述
- 下载链接(或许可证密钥,需突出显示)
- 下载限制与过期日期(如有)
- 产品访问/安装说明
- 问题反馈的支持联系方式
Shopify(Sky Pilot): 在 Sky Pilot → Settings → Email Template 下自定义
WooCommerce: 在 WooCommerce → Settings → Emails → Customer Processing Order 下自定义(自动包含下载链接)
BigCommerce: 在 Marketing → Transactional Emails → Order Status Notification 下自定义
Step 4: Monitor license key inventory
步骤4:监控许可证密钥库存
For license key products, set up alerts before keys run out:
- Sky Pilot: Go to License Keys → [Product] — shows remaining key count; Sky Pilot sends email alerts when stock drops below a threshold you configure
- License Manager for WooCommerce: Dashboard shows keys remaining per product with color-coded warnings
- Custom: Run a daily check and alert when available keys drop below 10
对于许可证密钥产品,在密钥耗尽前设置警报:
- Sky Pilot: 进入 License Keys → [Product]——显示剩余密钥数量;当库存低于你配置的阈值时,Sky Pilot 会发送邮件警报
- License Manager for WooCommerce: 仪表盘显示每个产品的剩余密钥数量,并带有颜色编码的警告
- 自定义方案: 每日运行检查,当可用密钥数量低于10时触发警报
Best Practices
最佳实践
- Never deliver digital products until payment is confirmed — trigger delivery from the payment confirmed webhook, not the order created event; card declines happen after order creation
- Use short-lived download links (60 seconds) for headless implementations — generate the URL fresh on each download page load, not when the page renders
- Set download limits for most products — 3–5 downloads is standard; unlimited for subscription access; limits prevent casual file sharing
- Store files in private buckets — never make your S3/GCS bucket public; all access must go through signed URL generation
- Monitor license key inventory — running out of keys causes support tickets and chargebacks; set up alerts when below 20% of original inventory
- Send a separate, dedicated delivery email — don't bundle license keys into the generic order confirmation; a focused delivery email is easier for customers to find and reference
- 付款确认前切勿交付数字产品——从付款确认的webhook触发交付,而非订单创建事件;订单创建后可能会出现信用卡拒付情况
- 无头架构使用短时效下载链接(60秒)——在每次下载页面加载时生成新的URL,而非页面渲染时生成
- 为大多数产品设置下载限制——标准为3–5次下载;订阅访问设为无限制;限制可防止随意文件共享
- 将文件存储在私有存储桶中——切勿将S3/GCS存储桶设为公开;所有访问必须通过签名URL生成
- 监控许可证密钥库存——密钥耗尽会导致支持工单和退款;当库存低于原始库存的20%时设置警报
- 发送单独的专属交付邮件——不要将许可证密钥捆绑到通用订单确认邮件中;聚焦的交付邮件更便于客户查找和参考
Common Pitfalls
常见陷阱
| Problem | Solution |
|---|---|
| Download link expires before customer clicks it | For headless builds, generate a fresh presigned URL on each page load, not at email send time |
| Digital product delivered after a failed payment | Trigger delivery only from the payment success webhook ( |
| License keys double-assigned | Use a database transaction for key assignment with an atomic status check — never select and then update in two steps |
| Customer loses access after account deletion | Store download access against |
| Large file download times out | Use S3 presigned URLs to deliver files directly from S3 to the customer's browser — never proxy through your server |
| 问题 | 解决方案 |
|---|---|
| 下载链接在客户点击前已过期 | 对于无头架构,在每次页面加载时生成新的预签名URL,而非发送邮件时生成 |
| 付款失败后仍交付数字产品 | 仅从付款成功的webhook触发交付(如Stripe中的 |
| 许可证密钥被重复分配 | 使用数据库事务进行密钥分配,并进行原子状态检查——切勿分两步执行查询和更新操作 |
| 客户删除账户后失去访问权限 | 将下载权限与 |
| 大文件下载超时 | 使用S3预签名URL直接从S3向客户浏览器交付文件——切勿通过你的服务器代理 |
Related Skills
相关技能
- @inventory-tracking
- @low-stock-alerts
- @product-data-modeling
- @inventory-tracking
- @low-stock-alerts
- @product-data-modeling