Skip to main content

Overview

A digital asset provider decides how a digital asset’s deliverable is produced at download time. Spree ships with one built-in provider, Spree::DigitalAssetProvider::File, which serves an uploaded file from private storage through a short-lived signed URL. That is the default: every asset with a blank provider_type uses it, so an uploaded file is simply the provider each asset already had. A custom provider replaces that upload with something you produce on demand — a license key minted by your billing system, an entitlement granted by internal software, a signed link to a file that lives on your own host. The customer’s download flows through the same authorized, counted grant either way; only the last step, “hand something over”, changes. Reach for a provider when the deliverable comes from outside Spree. If you only need to serve a file that a merchant uploads, the built-in File provider already does that — you don’t need to build anything.

What you will build

Two pieces: Unlike delivery-rate or tax providers, a digital asset provider does not resolve credentials through a Spree::Integration. It is host-app glue into your own software and owns its own configuration — an environment variable, an internal endpoint, a shared credential. The base gives it only the asset.

1. Implement the provider

Subclass Spree::DigitalAssetProvider::Base and implement #deliver. It receives the authorized Spree::DigitalLink (the customer’s download grant) and returns a Spree::DigitalDelivery.
app/models/spree/digital_asset_provider/license_key.rb
A Spree::DigitalDelivery carries exactly one of two shapes:
  • A redirect — set redirect_url. The customer is sent to it (an external file host, a signed storage URL). This is what File returns.
  • An inline body — set inline_value and content_type. The value is rendered directly as the response body — a license key as text/plain, a code image as image/png.
Return a blank delivery (or nil) to mean “nothing to hand over”. The download controller treats that as a failure and refuses the download without spending the customer’s allowance — see The download contract below.
#deliver must be safe to call before the download is charged. It runs while the grant is only checked, not yet spent, so a provider that raises or returns blank costs the customer nothing. Do any fallible work — the API call, the mint — inside #deliver, not after.

Per-asset settings

Some providers need a value that differs from one asset to the next — which license pool this asset draws from, which external product it maps to. Declare each as a setting, and the dashboard renders a small form when the merchant adds an asset backed by this provider:
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:
A provider that declares no settings skips the form entirely — adding it is a single click. Settings are for per-asset values only; a credential or endpoint shared across every asset belongs in the provider’s own configuration (an environment variable, Rails credentials), not a setting.

2. Register the provider

config/initializers/spree.rb
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.
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.

The download contract

Every download follows the same order, and a provider only participates in the last step:
  1. Check the grant — is it still authorizable (attempts left, not reset)?
  2. Check the window — is the signed-URL lifetime still positive?
  3. Produce the deliverable — call your #deliver. This is fallible and must not charge anything.
  4. Charge the grant — increment the download counter under a lock.
  5. Deliver — redirect, or render the inline body.
The order is deliberate: the deliverable is produced before the click is spent, so a provider outage or an exhausted external quota returns an honest error and the customer keeps their download. Keep #deliver free of side effects that assume the download will succeed.

Provider contract reference

DigitalDelivery

expires_in is the lifetime the store allows for any signed URL you build. It is already clamped to the store’s cap, so pass it straight through to whatever signs your link — never widen it.