@spree/docs 0.1.192 → 0.1.194

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -716,6 +716,115 @@ The API automatically returns the correct price based on the current currency an
716
716
 
717
717
  See the [Pricing](pricing.md) guide for details on Price Lists, Price Rules, and market-specific pricing.
718
718
 
719
+ ## Digital products
720
+
721
+ **A digital product is an ordinary product whose variant delivers without shipping — its [delivery profile](fulfillments.md) is a digital one, so buying it grants the customer a download instead of dispatching a parcel.** Nothing about the catalog model changes — you still have a product, its variants, and its prices. What that variant hands over is usually a **digital asset** it carries (an e‑book, a design file) or a value a provider mints on demand (a license key) — but the file is the optional deliverable, not what makes the product digital.
722
+
723
+ Two ideas are worth separating up front, because they are independent:
724
+
725
+ - **Being digital** is a delivery decision. A variant is digital when its [delivery profile](fulfillments.md) is a digital one — that variant needs no shipping address, and a cart made up entirely of digital variants skips the delivery step at checkout.
726
+ - **Carrying downloadable files** is a catalog decision. Any variant can own digital assets — including a physical one, so a boxed product can ship a warranty PDF or a setup guide alongside the goods.
727
+
728
+ Most digital products are both: a digital variant that carries the files it delivers. But the two are decoupled on purpose, so "ships nothing" and "hands over a file" can be mixed as a merchant needs.
729
+
730
+ ```mermaid
731
+ erDiagram
732
+ Product ||--|{ Variant : "has"
733
+ Variant ||--o{ DigitalAsset : "carries"
734
+ DigitalAsset ||--o{ DigitalLink : "granted as"
735
+ LineItem ||--o{ DigitalLink : "purchased in"
736
+ DigitalAsset {
737
+ string provider_type "blank = uploaded file"
738
+ int authorized_clicks "nullable, falls back to store"
739
+ int authorized_days "nullable, falls back to store"
740
+ }
741
+ DigitalLink {
742
+ string token "the download credential"
743
+ int access_counter "downloads spent"
744
+ }
745
+ ```
746
+
747
+ ### Assets live on the variant
748
+
749
+ A digital asset (`Spree::DigitalAsset`) belongs to a variant and holds either an uploaded file or a reference to a provider that produces the deliverable on demand (see [Where the file comes from](#where-the-file-comes-from) below). Uploaded files go to **private storage** — they are only ever served through a short‑lived, signed link, never a public URL.
750
+
751
+ You attach assets to a variant from the product's **Digital files** card in the dashboard, or through the Admin API nested under the product:
752
+
753
+
754
+ ```typescript Admin SDK
755
+ // 1. Ask for a private-storage upload slot, then PUT the file to the returned URL.
756
+ const { direct_upload, signed_id } = await adminClient.directUploads.create({
757
+ private: true, // digital files are only ever served through a signed link
758
+ blob: { filename: 'guide.pdf', byte_size: file.size, checksum, content_type: 'application/pdf' },
759
+ })
760
+ await fetch(direct_upload.url, { method: 'PUT', headers: direct_upload.headers, body: file })
761
+
762
+ // 2. Attach the uploaded blob to the product's default variant.
763
+ await adminClient.products.digitalAssets.create('prod_86Rf07xd4z', {
764
+ signed_id,
765
+ authorized_clicks: 5, // optional — omit to inherit the store default
766
+ authorized_days: 30,
767
+ })
768
+ ```
769
+
770
+ ```bash cURL
771
+ curl -X POST 'https://api.mystore.com/api/v3/admin/products/prod_86Rf07xd4z/digital_assets' \
772
+ -H 'X-Spree-API-Key: sk_xxx' \
773
+ -H 'Content-Type: application/json' \
774
+ -d '{ "signed_id": "eyJfcmFpbHMi...", "authorized_clicks": 5, "authorized_days": 30 }'
775
+ ```
776
+
777
+
778
+ > **WARNING:** Digital files must be uploaded to **private storage**. The Admin API refuses a blob that landed on the public service — attaching never moves a file between services, so a public upload would stay publicly readable while looking attached. Request the direct upload with `private: true`.
779
+
780
+ Replacing an asset's file keeps every download link that was already issued working: links resolve through the asset, not the underlying blob, so a merchant can swap a corrected file mid‑sale without breaking anyone's access.
781
+
782
+ ### A purchase grants download links
783
+
784
+ When an order is placed, digital assets fulfill themselves. The digital fulfillment provider runs automatically (it needs no address and no manual action) and, for each purchased unit, creates one **download link** (`Spree::DigitalLink`) per asset on the variant. Buy three copies of a two‑file bundle and the buyer gets six links. Re‑running fulfillment is idempotent — it never duplicates links.
785
+
786
+ A download link is the customer's **grant**: a globally unique token, a counter of downloads spent, and its own copy of the allowance. The token *is* the credential — it identifies the store on its own, which is why an emailed link works without any API key.
787
+
788
+ Customers reach their files two ways:
789
+
790
+ - **By email.** On order placement, `Spree::DigitalAssetMailer` sends a "your files are ready" message with the links — a message of its own, separate from the order confirmation, so it can be re‑sent from the order page later.
791
+ - **From their account.** A signed‑in customer sees every link they have ever been granted, across all their orders, through the customer downloads endpoint.
792
+
793
+ > **NOTE:** The files-ready email is only sent when the order actually has digital links and the store has consumer transactional emails enabled. A store that delivers files through its own storefront or webhooks can leave it off.
794
+
795
+ ### Downloading, and the allowance
796
+
797
+ A download is a `GET` against the link's token. Every download runs the same guarded sequence, and the customer's allowance is only spent once a deliverable is actually in hand:
798
+
799
+ 1. **The grant must be live** — attempts remaining, and not past its expiry.
800
+ 2. **The signed‑URL window must be open** — clamped to whatever is shorter, the store's link lifetime or the link's own remaining days.
801
+ 3. **The deliverable is produced** — see providers below. This step is allowed to fail.
802
+ 4. **The download is charged** — the counter is incremented under a lock.
803
+ 5. **The file is handed over** — a redirect to a signed URL, or an inline body.
804
+
805
+ The ordering is deliberate: producing the deliverable comes *before* charging the click, so a provider outage or a missing file returns an honest error and **costs the customer nothing**. Two allowances govern access, both falling back to store settings when the asset leaves them blank:
806
+
807
+ | Field | Meaning | Store default |
808
+ | --- | --- | :---: |
809
+ | `authorized_clicks` | How many times the file may be downloaded | `5` |
810
+ | `authorized_days` | How long after purchase the link stays valid | `7` |
811
+
812
+ Store‑wide defaults and their on/off switches live at **Settings → Store**; the separate `digital_asset_link_expire_time` preference caps how long a single signed URL lives (default 5 minutes, never more than an hour) because that URL is a bearer credential. When a customer runs out of downloads or a file was replaced mid‑flight, an admin can restore access by resetting the link from the order page, which zeroes the counter and restarts the clock.
813
+
814
+ Each successful download publishes an event you can subscribe to:
815
+
816
+ | Event | Published when |
817
+ | --- | --- |
818
+ | `digital_link.downloaded` | A customer successfully downloads a file (after the click is charged) |
819
+
820
+ ### Where the file comes from
821
+
822
+ By default a digital asset delivers its uploaded file. But the last step — "hand something over" — is pluggable through a **digital asset provider**, so the deliverable can instead be minted on demand: a license key from your billing system, an entitlement from internal software, or a signed link to a file on your own host. The purchase, the grant, the allowance, and the email are identical either way; only the production of the deliverable changes.
823
+
824
+ A blank `provider_type` on an asset means the built‑in file provider — the uploaded‑file behavior described above. Registering your own provider adds it as a source on the Digital files card, so a merchant can pick it when adding an asset.
825
+
826
+ > **TIP:** To build one, see the [Build a Custom Digital Asset Provider](../how-to/custom-digital-asset-provider.md) how‑to. It covers the `#deliver` contract, per‑asset settings, and registration.
827
+
719
828
  ## Categories
720
829
 
721
830
  There are two ways to group products, and they answer different questions.
@@ -934,6 +1043,7 @@ See [Sales Channels](channels.md) for the full channel lifecycle, including defa
934
1043
  - [Pricing](pricing.md) — Price Lists, Price Rules, and market-specific pricing
935
1044
  - [Inventory](inventory.md) — Stock management and backorders
936
1045
  - [Media](media.md) — Image management
1046
+ - [Build a Custom Digital Asset Provider](../how-to/custom-digital-asset-provider.md) — Deliver a license key or external file instead of an uploaded one
937
1047
  - [Translations](translations.md) — Translating product content
938
1048
  - [Search & Filtering](search-filtering.md) — Full-text search and Ransack filtering
939
1049
  - [Store SDK Products](../sdk/store/products.md) — Listing, fetching, filtering, and categories via `client.products`
@@ -76,7 +76,7 @@ setting :region, :select, in: %w[us eu], default: 'us'
76
76
  setting :auto_revoke, :boolean, default: false
77
77
  ```
78
78
 
79
- Field types are `:string`, `:number`, `:boolean`, and `:select` (pass `in:` for the choices). The merchant's answers are stored on the asset and read back through `digital_asset.provider_settings`, a plain hash keyed by the setting name:
79
+ Field types are `:string`, `:number`, `:boolean`, and `:select` (pass `in:` for the choices). Your declarations become the provider's `settings_schema`, which the admin `providers` endpoint exposes so the dashboard can render the form. The merchant's answers are stored on the asset — under one key, `metadata['provider']`, so they never collide with other developer metadata — and read back through `digital_asset.provider_settings`, a plain hash keyed by the setting name:
80
80
 
81
81
  ```ruby
82
82
  digital_asset.provider_settings['pool_name'] # => "winter-sale"
@@ -94,6 +94,8 @@ end
94
94
 
95
95
  Once registered, the provider appears as a source on the product's **Digital files** card: the **Add** button becomes a menu offering **Upload a file** alongside each registered provider. Picking your provider creates an asset with its `provider_type` set and no file attached.
96
96
 
97
+ > **NOTE:** Registration is what makes a `provider_type` valid. An asset validates that its `provider_type` names a registered provider, so an unregistered or misspelled class name is rejected with a 422 rather than failing at download time. A blank `provider_type` is always the built-in `File` provider.
98
+
97
99
  ## The download contract
98
100
 
99
101
  Every download follows the same order, and a provider only participates in the last step:
@@ -130,4 +132,5 @@ Spree::DigitalDelivery.new(inline_value: 'ABCD-1234-EFGH', content_type: 'text/p
130
132
  ## Related documentation
131
133
 
132
134
  - [Digital products](/use-case/digital-products/capabilities) — what the feature does for merchants
133
- - [Build a Custom Delivery Rate Provider](/v6/developer/how-to/custom-delivery-rate-provider) — a provider strategy that *does* use an integration, for contrast
135
+ - [Products](../core-concepts/products.md#digital-products) — how digital assets, links, and downloads fit together
136
+ - [Build a Custom Delivery Rate Provider](custom-delivery-rate-provider.md) — a provider strategy that *does* use an integration, for contrast
@@ -3,18 +3,75 @@ title: Sell Digital Products
3
3
  description: Digital goods in Spree — a digital delivery profile, instant fulfillment without a shipping address, and delivering files or entitlements after payment.
4
4
  ---
5
5
 
6
- Digital products skip the warehouse: no stock to reserve, no address to collect, no carrier to call. In Spree 6 that behavior comes from the product's delivery profile — assign a digital profile and checkout, fulfillment and the customer experience adapt.
6
+ Digital products skip the warehouse: no stock to reserve, no address to collect, no carrier to call. In Spree 6 that behavior comes from the product's delivery profile — assign a digital profile and checkout, fulfillment and the customer experience adapt. This guide walks through selling an uploaded file end to end.
7
+
8
+ For the concepts behind each piece — assets, links, allowances, providers — see [Products → Digital products](../core-concepts/products.md#digital-products). This page is the practical walkthrough.
7
9
 
8
10
  ## What you will build
9
11
 
10
- - **Digital delivery profile** — products that require no shipping address and complete checkout without a delivery step.
11
- - **Instant fulfillment** — the digital fulfillment provider fulfills on payment rather than on dispatch.
12
- - **Delivery of the goods** — attaching files or granting entitlements, and notifying the customer.
13
- - **Mixed carts** — physical and digital items in one order, split into separate fulfillments automatically.
12
+ - **A digital variant** — one whose delivery profile requires no shipping address, so a fully digital cart completes checkout without a delivery step.
13
+ - **A downloadable file on it** — uploaded to private storage and attached as a digital asset.
14
+ - **Automatic delivery** — a download link granted on order placement, and an email that sends it.
15
+ - **Mixed carts** — physical and digital items in one order, handled without extra work.
16
+
17
+ ## 1. Make the product digital
18
+
19
+ A variant is digital when its [delivery profile](../core-concepts/fulfillments.md) is a digital one. A store ships with a built-in **Digital** delivery profile; assign it to the product and the product's variants stop asking for a shipping address.
20
+
21
+
22
+ ```typescript Admin SDK
23
+ // Assign the store's digital delivery profile to the product.
24
+ const { data: profiles } = await adminClient.deliveryProfiles.list()
25
+ const digital = profiles.find((p) => p.kind === 'digital')
26
+
27
+ await adminClient.products.update('prod_86Rf07xd4z', {
28
+ delivery_profile_id: digital.id,
29
+ })
30
+ ```
31
+
32
+ ```bash cURL
33
+ curl -X PATCH 'https://api.mystore.com/api/v3/admin/products/prod_86Rf07xd4z' \
34
+ -H 'X-Spree-API-Key: sk_xxx' \
35
+ -H 'Content-Type: application/json' \
36
+ -d '{ "delivery_profile_id": "dp_digital" }'
37
+ ```
38
+
39
+
40
+ > **NOTE:** A product's delivery profile is auto-assigned when it is created — from its product type's template, or the store default. You only set it explicitly to change it. Selling from the dashboard, this is the **Shipping profile** field on the product page.
41
+
42
+ ## 2. Attach the file
43
+
44
+ Upload the file to **private storage** and attach it to the variant as a digital asset. Private storage matters: a digital file is only ever served through a short-lived signed link, so the Admin API refuses a blob that landed on the public bucket.
45
+
46
+ ```typescript Admin SDK
47
+ // Request a private upload slot, PUT the file, then attach the blob.
48
+ const { direct_upload, signed_id } = await adminClient.directUploads.create({
49
+ private: true,
50
+ blob: { filename: 'guide.pdf', byte_size: file.size, checksum, content_type: 'application/pdf' },
51
+ })
52
+ await fetch(direct_upload.url, { method: 'PUT', headers: direct_upload.headers, body: file })
53
+
54
+ await adminClient.products.digitalAssets.create('prod_86Rf07xd4z', { signed_id })
55
+ ```
56
+
57
+ Leave `authorized_clicks` and `authorized_days` off to inherit the store's download limits, or set them per file to mix evergreen downloads with time-limited ones. See [the allowance](../core-concepts/products.md#downloading-and-the-allowance) for how the limits resolve.
58
+
59
+ > **TIP:** You can attach a file to a **physical** variant too — a manual or warranty PDF that ships alongside the goods. Carrying files and being digital are independent: the physical variant still needs an address, and it also grants a download link.
60
+
61
+ ## 3. Let the order deliver itself
62
+
63
+ There is no step three to configure. When the order is placed, the digital fulfillment provider runs automatically — it needs no address and no manual dispatch — and grants the buyer one download link per file, per purchased unit. `Spree::DigitalAssetMailer` then emails the links as a message of its own, so it survives a replaced order-confirmation template and can be re-sent from the order page.
64
+
65
+ The customer downloads by following the link's token; each download is counted against the allowance and publishes a `digital_link.downloaded` event. If they exhaust their downloads or you replace the file mid-sale, an admin restores access by resetting the link from the order page.
66
+
67
+ > **NOTE:** To deliver something other than an uploaded file — a license key minted on demand, an entitlement from your own system — the delivery step is pluggable. See [Build a Custom Digital Asset Provider](custom-digital-asset-provider.md).
68
+
69
+ ## 4. Mixed carts work on their own
70
+
71
+ A cart with both physical and digital items needs nothing special. Spree splits the order into separate fulfillments per delivery profile, so the physical items collect an address and ship while the digital items skip straight to granting links. The delivery step only appears when at least one item actually needs shipping; an all-digital cart never sees it.
14
72
 
15
- ## Outline
73
+ ## Related documentation
16
74
 
17
- 1. Create a digital delivery profile and assign products
18
- 2. Configure the digital fulfillment provider
19
- 3. Deliver files or entitlements on fulfillment
20
- 4. Handle mixed physical and digital orders
75
+ - [Products Digital products](../core-concepts/products.md#digital-products) the concepts: assets, links, allowances, providers
76
+ - [Build a Custom Digital Asset Provider](custom-digital-asset-provider.md) deliver a key or external file instead of an upload
77
+ - [Fulfillments](../core-concepts/fulfillments.md) delivery profiles, providers, and how orders are fulfilled
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.192",
3
+ "version": "0.1.194",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",