Setting Up Digital Goods in 1C-Bitrix
Digital product sold, money charged, email with file didn't arrive. Or a download link arrived but works indefinitely. Or a file is accessible without payment via direct URL enumeration. All three problems result from incorrect product type configuration and file protection.
Product Type and File Field
A digital product in Bitrix is a product with type TYPE_ELECTRONICAL (value 5) in the TYPE field of the b_catalog_product table. The download file is attached via an infoblock property of type "File" (FILE) or via the special mechanism b_catalog_product — field FILE_ID, which references b_file.
Setting the type and file:
\Bitrix\Catalog\ProductTable::update($productId, [
'TYPE' => \Bitrix\Catalog\ProductTable::TYPE_ELECTRONICAL,
]);
// Attaching file via infoblock property
\CIBlockElement::SetPropertyValuesEx($productId, $iblockId, [
'DIGITAL_FILE' => [
'VALUE' => \CFile::MakeFileArray('/path/to/file.zip'),
],
]);
File Protection from Direct Access
Digital product files should not be placed in /upload/ with direct HTTP access. The standard Bitrix mechanism is the /upload/protected/ folder with a rule in .htaccess or nginx prohibiting direct access. Download occurs via a protected URL generated by the system.
nginx configuration for protecting the directory:
location /upload/protected/ {
deny all;
return 403;
}
File access is provided through the bitrix:sale.personal.order component or a separate handler that checks for a paid order with this product and generates a temporary URL.
Delivering File After Payment
The standard file delivery mechanism is the OnSaleOrderPaid event handler in the sale module. When an order is paid, the system goes through basket items, finds products with TYPE = 5, and sends a download link to the customer's email.
Limiting download count and link expiration are managed by product properties. The standard catalog module doesn't have a built-in download counter — this is added custom via a separate table or order properties.
Implementing file delivery with permission checks:
// In the download request handler
$orderId = (int)$_GET['order'];
$productId = (int)$_GET['product'];
$hash = $_GET['hash'];
// Check token
$expected = md5($orderId . $productId . $userId . SITE_ID . $_SERVER['HTTP_HOST']);
if ($hash !== $expected) {
die('Access denied');
}
// Check order is paid
$order = \Bitrix\Sale\Order::load($orderId);
if (!$order || $order->isPaid() !== true) {
die('Order not paid');
}
// Deliver file
$fileId = getDigitalFileByProduct($productId);
$file = \Bitrix\Main\IO\File::createInstance(\CFile::GetPath($fileId));
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($file->getPath()) . '"');
$file->readFile();
Link Expiration
A link with temporary token must expire. Simple approach — include timestamp in the signature and reject requests older than N hours when checking:
$timestamp = (int)$_GET['ts'];
if (time() - $timestamp > 86400) { // 24 hours
die('Link expired');
}
$expected = md5($orderId . $productId . $userId . $timestamp . SITE_KEY);
To limit download count, use a table with records (order_id, product_id, user_id, downloads_count, max_downloads). On each download, the counter increases; exceeding the limit blocks access.
Stock and Repurchase
Digital products typically have no physical stock. In b_catalog_product for them, it's recommended to set QUANTITY_TRACE = 'N' and CAN_BUY_ZERO = 'Y' — then stock is not tracked and the product is always available for purchase. With QUANTITY_TRACE = 'Y' and stock 0, the store will block the purchase, which is meaningless for digital goods.







