# Shoppable Videos

The shopping feature enables your audience to browse and purchase products while watching videos. With this feature, users can view product details, explore available options (colors, sizes, etc.), and add items to their cart without leaving the video experience.

> **Note**: The SDK does not provide payment processing. You are responsible for managing the shopping cart and checkout flow through the provided callbacks.

## Overview

Firework SDK provides:

* **Product Display**: Built-in UI to display product lists and handle user interactions
* **Shopping Cart**: Callbacks for cart management and checkout flow
* **Product Details**: Integrated product detail pages (PDP) with customizable actions
* **Purchase Tracking**: Analytics integration for conversion tracking

## UI Themes

The SDK provides two color themes for the shopping interface:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        theme = ShoppingTheme.LIGHT  // or ShoppingTheme.DARK
    )
)
```

|            Dark Theme            |            Light Theme           |
| :------------------------------: | :------------------------------: |
| ![](/files/bsTtqH0K1AJuvWGJK7aC) | ![](/files/JKxhLExxW22eE2IXykxX) |

## New Product Cards

> **Important**: New Product Cards are only available when using **Player Version 2 (V2)**. The Version 1 product cards described below are maintained for backward compatibility but will not receive new features.

The Firework SDK provides an enhanced product card experience in Player Version 2 with improved UI, better performance, and new features. To access the new product cards, you must enable V2 for both the video player and livestream player.

|      Single Product Card(V2)     |      Single Product Card(V1)     |     Multiple Product Card(V2)    |     Multiple Product Card(V1)    |
| :------------------------------: | :------------------------------: | :------------------------------: | :------------------------------: |
| ![](/files/FT3XT2hEb0TMyI13RhQR) | ![](/files/FBleunnsPAjfkJjs67W1) | ![](/files/2yIvyHjOKKPQjgoZtZ2L) | ![](/files/8HQCPhCoIAKUMapb4XUT) |

### Why Use Player Version 2?

**✅ All future updates and new features are only available in V2**\
\&#xNAN;**⚠️ V1 is in maintenance mode and will not receive new features**

Player Version 2 provides:

* **Enhanced Product Cards**: Improved UI with better animations and interactions
* **Better Performance**: Faster rendering and smoother scrolling
* **Modern Design**: Updated visual design aligned with current standards
* **Active Development**: Ongoing improvements and new features
* **Bug Fixes**: Priority bug fixes and updates

### How to Enable Player Version 2

To use the new product cards, enable V2 for both video player and livestream player **before** SDK initialization:

#### Step 1: Set Player Versions in Application Class

```kotlin
import android.app.Application
import com.firework.sdk.FireworkSdk
import com.firework.sdk.FireworkSdkConfig
import com.firework.sdk.player.FwVideoPlayerVersion
import com.firework.sdk.player.FwLivestreamPlayerVersion
import com.firework.imageloading.glide.GlideImageLoaderFactory

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        
        // ✅ STEP 1: Set player versions to V2 (BEFORE SDK initialization)
        FireworkSdk.setVideoPlayerVersion(FwVideoPlayerVersion.V2)
        FireworkSdk.setLivestreamPlayerVersion(FwLivestreamPlayerVersion.V2)
        
        // STEP 2: Build SDK configuration
        val config = FireworkSdkConfig.Builder(context = this)
            .clientId("YOUR_CLIENT_ID")
            .imageLoader(GlideImageLoaderFactory.createInstance(context = this))
            .build()
        
        // STEP 3: Initialize SDK
        FireworkSdk.init(
            fireworkSdkConfig = config,
            onSuccess = {
                Log.d("FireworkSDK", "SDK initialized with V2 players")
            },
            onError = { error ->
                Log.e("FireworkSDK", "Initialization failed", error)
            }
        )
    }
}
```

#### Step 2: Enable Livestream Support (If Needed)

If you're using livestream features, also add the livestream player dependency:

```kotlin
dependencies {
    // Video player (required)
    implementation("com.firework.external:FireworkSDK:X.Y.Z")
    
    // Livestream player (only if you need livestream features)
    implementation("com.firework.external.livestream:singleHostPlayer:X.Y.Z")
}
```

And configure the livestream initializer:

```kotlin
import com.firework.external.livestream.singlehost.SingleHostLivestreamPlayerInitializer

val config = FireworkSdkConfig.Builder(context = this)
    .clientId("YOUR_CLIENT_ID")
    .imageLoader(GlideImageLoaderFactory.createInstance(context = this))
    .addLivestreamPlayerInitializer(SingleHostLivestreamPlayerInitializer())
    .build()
```

### Available Player Versions

**Short Video Player:**

```kotlin
FireworkSdk.setVideoPlayerVersion(FwVideoPlayerVersion.V1)  // Default, maintenance mode
FireworkSdk.setVideoPlayerVersion(FwVideoPlayerVersion.V2)  // Recommended, actively developed
```

**Livestream Player:**

```kotlin
FireworkSdk.setLivestreamPlayerVersion(FwLivestreamPlayerVersion.V1)  // Default, maintenance mode
FireworkSdk.setLivestreamPlayerVersion(FwLivestreamPlayerVersion.V2)  // Recommended, actively developed
```

### Important Notes

* ⚠️ Player versions **must** be set before `FireworkSdk.init()`
* ✅ Both players are independent - you can use V2 for one and V1 for the other
* ✅ However, we strongly recommend using V2 for both players
* ✅ V1 will continue to work but will not receive new features
* ✅ New product cards are automatically enabled when using V2 players

### Verification

After enabling V2, you can verify the product cards are using the new version by observing:

* Improved animations when scrolling through products
* Enhanced visual design with updated spacing and typography
* Better touch feedback on product card interactions
* Smoother transitions when opening product details

***

### Product Card Click Handler

When a user taps a product card, the SDK determines the navigation behavior using a three-level priority system. This gives you full flexibility: from zero-configuration defaults to complete custom control.

#### Priority Order

```mermaid
flowchart TD
    Click["User taps product card"] --> CheckListener{"OnProductCardClickListener set?"}
    CheckListener -->|"Yes"| CallListener["Call listener.onProductCardClick()"]
    CallListener --> ListenerResult{"Returns true?"}
    ListenerResult -->|"true"| HostHandles["Host app handles navigation"]
    ListenerResult -->|"false"| CheckBehavior
    CheckListener -->|"No"| CheckBehavior{"ProductCardClickBehavior type?"}
    CheckBehavior -->|"OpenExternalLink"| ValidateURL{"Product URL available?"}
    ValidateURL -->|"Yes"| OpenExternal["SDK opens external URL via ACTION_VIEW"]
    ValidateURL -->|"No"| FallbackPDP["Report error"]
    CheckBehavior -->|"OpenProductDetails or null"| OpenPDP["SDK opens built-in PDP"]
    OpenExternal --> CheckPip{"enterPip = true?"}
    CheckPip -->|"Yes"| EnterPip["SDK enters PiP mode"]
    CheckPip -->|"No"| Done["Done"]
```

| Priority    | Mechanism                                     | When to use                                                                               |
| ----------- | --------------------------------------------- | ----------------------------------------------------------------------------------------- |
| 1 (Highest) | `setOnProductCardClickListener`               | Full custom control: open your own activity, track analytics, or perform any custom logic |
| 2           | `ProductCardClickBehavior.OpenExternalLink`   | Zero-configuration external navigation: SDK opens the product URL automatically           |
| 3 (Default) | `ProductCardClickBehavior.OpenProductDetails` | SDK opens the built-in product details page (PDP)                                         |

> **Backward compatible**: If you do not configure anything, the SDK opens the built-in PDP (same as previous versions).

***

#### Path 1: Custom Listener (Highest Priority)

Use `setOnProductCardClickListener` when you need full control over what happens when a product card is clicked.

```kotlin
FireworkSdk.shopping.setOnProductCardClickListener { productId, unitId, productWebUrl, videoInfo, product ->
    // Perform custom navigation
    openProductPage(productWebUrl)

    // Optionally enter Picture-in-Picture mode
    FireworkSdk.enterPip()

    // Return true: your app handled navigation, SDK does nothing further
    // Return false: SDK falls through to ProductCardClickBehavior (Path 2/3)
    true
}
```

**Return value matters:**

* `true` — Your app handled navigation. The SDK takes no further action.
* `false` — The SDK falls through to the `ProductCardClickBehavior` setting (Path 2 or Path 3).

To remove the listener:

```kotlin
FireworkSdk.shopping.setOnProductCardClickListener(null)
```

***

#### Path 2: ViewOptions-Driven External Link (Zero Configuration)

Use `ProductCardClickBehavior.OpenExternalLink` when you want the SDK to automatically open the product's external URL without writing any listener code.

```kotlin
val viewOptions = ViewOptions.Builder()
    .playerOption(
        PlayerOption.Builder()
            .productCardClickBehavior(
                ProductCardClickBehavior.OpenExternalLink()
            )
            .build()
    )
    .build()

videoFeedView.init(viewOptions)
```

The SDK will:

1. Extract the product URL from `product.units.firstOrNull()?.url`
2. Open it via `Intent.ACTION_VIEW`
3. If the URL is missing, report an error&#x20;

**With custom Intent flags:**

```kotlin
ProductCardClickBehavior.OpenExternalLink(
    launchFlags = Intent.FLAG_ACTIVITY_SINGLE_TOP
)
```

**With automatic PiP mode:**

When `enterPip = true`, the SDK automatically enters Picture-in-Picture mode after opening the external link, allowing the video to continue playing in a small overlay while the user browses the product page.

```kotlin
ProductCardClickBehavior.OpenExternalLink(
    enterPip = true
)
```

***

#### Path 3: Built-in Product Details Page (Default)

If no listener is set and `productCardClickBehavior` is `OpenProductDetails` (or not configured), the SDK opens its built-in product details page.

```kotlin
// Explicit (equivalent to not setting it at all)
PlayerOption.Builder()
    .productCardClickBehavior(ProductCardClickBehavior.OpenProductDetails)
    .build()
```

***

#### Combining Listener and ViewOptions

You can set both a listener and a `ProductCardClickBehavior`. The listener always gets first priority:

```kotlin
// Configure ViewOptions as fallback
val viewOptions = ViewOptions.Builder()
    .playerOption(
        PlayerOption.Builder()
            .productCardClickBehavior(
                ProductCardClickBehavior.OpenExternalLink(enterPip = true)
            )
            .build()
    )
    .build()

videoFeedView.init(viewOptions)

// Listener takes priority when set
FireworkSdk.shopping.setOnProductCardClickListener { productId, unitId, productWebUrl, videoInfo, product ->
    if (shouldHandleInApp(product)) {
        navigateToProductPage(productId)
        true  // Handled by app
    } else {
        false // Fall through to OpenExternalLink behavior
    }
}
```

In this example:

* If the listener returns `true`, the app handles navigation.
* If the listener returns `false`, the SDK opens the external link and enters PiP mode (as configured in ViewOptions).

## Version 1 Product Cards Configuration

Product cards display a list of available products in the video. Each card represents a product with its image, title, price, and action button.

### Configurable Properties

* **Corner Radius**: Rounded corners of product cards
* **CTA Button Label**: "Shop now" or "Buy now"
* **CTA Button Visibility**: Show or hide the action button
* **Click Action**: Custom handler for card clicks

### Corner Radius

The default corner radius is `4dp`. You can customize it:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productCardsOptions = ProductCardsOptions.Default(
            cornerRadius = resources.getDimensionPixelSize(R.dimen.corner_radius)
        ),
    ),
)
```

**Visual Comparison:**

<table><thead><tr><th width="384" align="center">24dp radius</th><th align="center">4dp radius</th></tr></thead><tbody><tr><td align="center"><img src="/files/BqdySNvtbqkXElCqvEV5" alt=""></td><td align="center"><img src="/files/dr3xaFtZdmnAit96BrHd" alt=""></td></tr></tbody></table>

### CTA Button Label

The default label is "Shop now". You can change it to "Buy now":

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productCardsOptions = ProductCardsOptions.Default(
            ctaButtonText = ProductCardsOptions.Text.BUY_NOW
            // or ProductCardsOptions.Text.SHOP_NOW
        ),
    ),
)
```

<figure><img src="/files/PkHwSmGA9ooaTRRZamkA" alt="" width="303"><figcaption><p>Product card CTA button</p></figcaption></figure>

### CTA Button Visibility

Control whether the CTA button is shown on product cards:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productCardsOptions = ProductCardsOptions.Default(
            isCtaVisible = true  // or false
        ),
    ),
)
```

**Visual Comparison:**

|        "Shop now" visible        |        "Shop now" hidden:        |
| :------------------------------: | :------------------------------: |
| ![](/files/ZlkjpTNuiKb3kkh2CtCW) | ![](/files/CCMRl9hFC0RQCqJwmmiT) |

## Custom Product Cards

> **Important**: Custom product cards require enablement by your Firework account manager.

The SDK allows you to provide a fully custom view for product cards:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        theme = ShoppingTheme.LIGHT,
        productCardsOptions = ProductCardsOptions.Custom(
            widthPx = width,
            heightPx = height,
            spaceBetweenItems = spaceBetweenItems,
            cardViewStartEndPadding = padding,
            customViewProvider = { MyCustomProductCard(this) },
        ),
    ),
)
```

### Implementing Custom Product Card View

Create a custom view by extending `FwProductCardView`:

```kotlin
class MyCustomProductCard @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyle: Int = 0,
) : FwProductCardView(context, attrs, defStyle) {
    
    private val title: TextView
    private val price: TextView
    private val image: ImageView
    
    init {
        inflate(context, R.layout.custom_product_card_item, this)
        title = findViewById(R.id.title)
        price = findViewById(R.id.price)
        image = findViewById(R.id.image)
    }

    override fun bind(product: ProductCardDetails, videoInfo: VideoInfo) {
        title.text = product.productTitle
        price.text = "${product.price?.amount ?: 0.0} ${product.price?.currencyCode}"
        
        Glide.with(image.context)
            .load(product.productImageUrl)
            .placeholder(R.drawable.ic_product_placeholder)
            .error(R.drawable.ic_product_placeholder)
            .into(image)
    }
}
```

### Important Notes

* **Size Requirements**: Width and height must be in pixels. The SDK may limit card size to respect the [safe zone specifications](https://help.firework.com/safe-zone-specifications-for-firework-videos).
* **Click Handling**: Custom product cards cannot have clickable elements. All clicks are intercepted by the SDK.

## Shopping CTA Button

The shopping CTA button appears on the product detail page and triggers the add-to-cart or shop-now action.

<figure><img src="/files/QhzVHycUOYhWEzzjJrlM" alt="" width="303"><figcaption><p>Product details page - CTA button (3)</p></figcaption></figure>

### CTA Button Click Listener

Handle clicks on the shopping CTA button:

```kotlin
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        FireworkSdk.shopping.setOnCtaButtonClicked { productId, unitId, productWebUrl, videoInfo ->
            // Handle CTA click
            // Add product to cart, navigate to checkout, etc.
        }
     }

    override fun onDestroy() {
        FireworkSdk.shopping.setOnCtaButtonClicked(null)
        super.onDestroy()
    }
}
```

### CTA Button Status

For long-running operations (e.g., adding to cart via API), notify the SDK of the operation status:

```kotlin
FireworkSdk.shopping.setOnCtaButtonClicked { productId, unitId, productWebUrl, videoInfo ->
    // Set loading state
    FireworkSdk.shopping.setCtaButtonStatus(CtaButtonStatus.Loading)
    
    try {
        val result = api.addToCart(productId, unitId)
        
        if (result.isSuccess) {
            FireworkSdk.shopping.setCtaButtonStatus(CtaButtonStatus.Success)
        } else {
            FireworkSdk.shopping.setCtaButtonStatus(CtaButtonStatus.Error)
        }
    } catch (e: Exception) {
        FireworkSdk.shopping.setCtaButtonStatus(CtaButtonStatus.Error)
    }
}
```

**Status Types:**

* `CtaButtonStatus.Loading` - Operation in progress
* `CtaButtonStatus.Success` - Operation completed successfully
* `CtaButtonStatus.Error` - Operation failed

> **Note**: If status is not provided within 10 seconds, the SDK assumes the operation failed.

### CTA Button Text

Choose between "Add to cart" and "Shop now" labels:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productDetailsOptions = ProductDetailsOptions(
                shoppingCtaButtonOptions = ShoppingCtaButtonOptions(
                        text = ShoppingCtaButtonOptions.Text.SHOP_NOW
                // or ShoppingCtaButtonOptions.Text.ADD_TO_CART
                ),
        ),
     )
 )
```

**Usage Guidelines:**

* **Add to cart**: Use when shopping cart is enabled
* **Shop now**: Use for direct navigation to product pages without cart

## Customize Shopping CTA Button

You can customize the "Add to Cart" button appearance by overriding the `FwShoppingCtaButtonStyle` in your app's theme:

### Basic Styling

```xml
<resources>
   <style name="FwShoppingCtaButtonStyle" parent="FwShoppingCtaButtonParentStyle">
        <item name="android:backgroundTint">@color/backgroundColor</item>
        <item name="android:textColor">@color/myTextColor</item>
        <item name="android:textSize">16sp</item>
    </style>
</resources>
```

### Shape Customization

To customize button shape (rounded corners, etc.):

```xml
<resources>
    <style name="FwShoppingCtaButtonStyle" parent="FwShoppingCtaButtonParentStyle">
        <item name="shapeAppearanceOverlay">@style/MyAddToCartButtonShapeStyle</item>
    </style>
    
    <style name="MyAddToCartButtonShapeStyle">
        <item name="cornerFamily">rounded</item> <!-- "rounded" or "cut" -->
        <item name="cornerSizeTopLeft">6dp</item>
        <item name="cornerSizeBottomLeft">6dp</item>
        <item name="cornerSizeBottomRight">6dp</item>
        <item name="cornerSizeTopRight">6dp</item>
    </style>
</resources>
```

## Shopping Cart

The SDK does not manage the shopping cart internally. You are responsible for:

* Maintaining cart state
* Managing cart items
* Handling checkout flow

The SDK provides callbacks and configuration options for cart integration.

### Cart Behavior

Configure how the shopping cart behaves using `CartBehaviour`:

```kotlin
FireworkSdk.shopping.setShoppingCartBehaviour(behaviour: CartBehaviour)
```

**Available Behaviors:**

1. **CartBehaviour.NoCart** (Default)
   * Shopping cart icon is hidden
   * Suitable for "Shop now" mode without cart
2. **CartBehaviour.Callback**
   * Cart icon is shown
   * `OnCartActionListener.onCartClicked` is triggered when clicked
   * Use when opening cart as a separate activity
3. **CartBehaviour.Embedded**
   * Cart icon is shown
   * Fragment from `EmbeddedCartFactory` is displayed when clicked
   * Requires calling `setEmbeddedCartFactory` first

### Embedded Cart Example

```kotlin
class MainActivity : AppCompatActivity() {

    private val embeddedCartFactory = object : EmbeddedCartFactory {
        override fun getInstance(): Fragment {
            return CustomShoppingCartFragment()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Set the factory
        FireworkSdk.shopping.setEmbeddedCartFactory(embeddedCartFactory)
        
        // Enable embedded cart behavior
        FireworkSdk.shopping.setShoppingCartBehaviour(
            Shopping.CartBehaviour.Embedded(title = "Shopping Cart")
        )
    }
}
```

### Cart Click Listener

> **Important**: This listener only works with `CartBehaviour.Callback`.

<figure><img src="/files/QhzVHycUOYhWEzzjJrlM" alt="" width="303"><figcaption><p>Product details page - Cart icon (1)</p></figcaption></figure>

```kotlin
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Set callback behavior
        FireworkSdk.shopping.setShoppingCartBehaviour(Shopping.CartBehaviour.Callback)
        
        // Handle cart clicks
        FireworkSdk.shopping.setOnCartClickListener { videoInfo ->
            // Open cart activity or fragment
            startActivity(Intent(this, CartActivity::class.java))
        }
    }

    override fun onDestroy() {
        FireworkSdk.shopping.setOnCartClickListener(null)
        super.onDestroy()
    }
}
```

## Product Detail Page (PDP)

### PDP Link Button Click Listener

<figure><img src="/files/QhzVHycUOYhWEzzjJrlM" alt="" width="303"><figcaption><p>Product details page - PDP link (2)</p></figcaption></figure>

Handle clicks on the PDP link button:

```kotlin
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        FireworkSdk.shopping.setOnProductLinkClickListener { productId, unitId, productWebUrl, videoInfo ->

            // Let fullscreen player jump into picture in picture mode
            FireworkSdk.enterPip()
            // Handle custom navigation
            // Return true to handle it yourself, false to let SDK handle it
            
            openProductPage(productWebUrl)
            return true
        }
    }

    override fun onDestroy() {
        FireworkSdk.shopping.setOnProductLinkClickListener(null)
        super.onDestroy()
    }
}
```

**Return Value:**

* `true` - Your app handles the navigation
* `false` - SDK opens the product page

### PDP Link Button Visibility

Control whether the PDP link button is shown:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
        ShoppingViewOptions(
        productDetailsOptions = ProductDetailsOptions(
                    linkButtonOptions = LinkButtonOptions(isVisible = false),
                )
        )
 )
```

## Product Hydration

Product hydration allows you to provide real-time product data from your backend. See [Product Hydration](/firework-for-developers/android-sdk/integration-guide/shoppable-videos/product-hydration.md) for detailed information.

## "Shop Now" Mode

Configure the SDK for direct product page navigation without a shopping cart:

```kotlin
private fun setupShopNowMode() {
    with(FireworkSdk.shopping) {
        // Hide cart button
        setShoppingCartBehaviour(Shopping.CartBehaviour.NoCart)
        
        // Configure PDP options
        setShoppingViewOptions(
            ShoppingViewOptions(
                productDetailsOptions = ProductDetailsOptions(
                    linkButtonOptions = LinkButtonOptions(isVisible = false),
                    shoppingCtaButtonOptions = ShoppingCtaButtonOptions(
                        text = ShoppingCtaButtonOptions.Text.SHOP_NOW
                    ),
                ),
            ),
        )
        
        // Handle CTA clicks
        setOnCtaButtonClicked { productId, unitId, productWebUrl, videoInfo ->
            openWebUrlInBrowser(productWebUrl)
            FireworkSdk.enterPip()  // Move player to PIP
        }
    }
}
```

## Error Handling

Implement `OnShoppingErrorListener` to receive shopping error events:

```kotlin
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        FireworkSdk.shopping.setOnShoppingErrorListener { error ->
            when (error) {
                is ShoppingError.AddToCartError -> handleCartError(error)
                is ShoppingError.ShowProductInfoError -> handleProductError(error)
            }
        }
    }
    
    private fun handleCartError(error: ShoppingError.AddToCartError) {
        when (error) {
            ShoppingError.AddToCartError.NullProductId -> 
                Log.e(TAG, "Product ID is null")
            ShoppingError.AddToCartError.NullProductUnitId -> 
                Log.e(TAG, "Product unit ID is null")
            ShoppingError.AddToCartError.NullCartActionListener -> 
                Log.e(TAG, "Cart listener not set")
            ShoppingError.AddToCartError.Timeout -> 
                Log.e(TAG, "Add to cart timeout")
        }
    }
    
    private fun handleProductError(error: ShoppingError.ShowProductInfoError) {
        when (error) {
            ShoppingError.ShowProductInfoError.FailedToLaunchUrl -> 
                Log.e(TAG, "Failed to launch product URL")
            ShoppingError.ShowProductInfoError.NullProductId -> 
                Log.e(TAG, "Product ID is null")
            ShoppingError.ShowProductInfoError.NullUnitId -> 
                Log.e(TAG, "Unit ID is null")
            ShoppingError.ShowProductInfoError.NullProductWebUrl -> 
                Log.e(TAG, "Product URL is null")
        }
    }

    override fun onDestroy() {
        FireworkSdk.shopping.setOnShoppingErrorListener(null)
        super.onDestroy()
    }
}
```

### Error Types

```kotlin
sealed interface ShoppingError {
    sealed interface AddToCartError : ShoppingError {
        object NullProductId : AddToCartError
        object NullProductUnitId : AddToCartError
        object NullCartActionListener : AddToCartError
        object Timeout : AddToCartError
    }

    sealed interface ShowProductInfoError : ShoppingError {
        object FailedToLaunchUrl : ShowProductInfoError
        object NullProductId : ShowProductInfoError
        object NullUnitId : ShowProductInfoError
        object NullProductWebUrl : ShowProductInfoError
    }
}
```

## Programmatic Control

### Dismiss Shopping UI

Close the shopping interface programmatically:

```kotlin
FireworkSdk.shopping.dismiss()
```

### Open Shopping Cart

Open the shopping cart programmatically:

```kotlin
FireworkSdk.shopping.openShoppingCart()
```

## Purchase Tracking

Track completed purchases for analytics and conversion tracking.

### Function Signature

```kotlin
fun trackPurchase(
    orderId: String,
    value: Double?,
    currencyCode: String?,
    countryCode: String?,
    additionalInfo: Map<String, String>,
    products: List<ProductItem>?,
    shippingPrice: Double?,
    subtotal: Double?,
)
```

### Parameters

| Parameter        | Type                  | Required | Description                                       |
| ---------------- | --------------------- | -------- | ------------------------------------------------- |
| `orderId`        | `String`              | ✅        | Unique identifier for the order/transaction       |
| `value`          | `Double?`             | ❌        | Total transaction value (defaults to 0.0 if null) |
| `currencyCode`   | `String?`             | ❌        | Currency code (defaults to "USD" if null)         |
| `countryCode`    | `String?`             | ❌        | Country code (defaults to device locale if null)  |
| `additionalInfo` | `Map<String, String>` | ✅        | Additional custom tracking information            |
| `products`       | `List<ProductItem>?`  | ❌        | List of purchased products                        |
| `shippingPrice`  | `Double?`             | ❌        | Shipping cost for the order                       |
| `subtotal`       | `Double?`             | ❌        | Subtotal before shipping and taxes                |

### ProductItem Structure

```kotlin
data class ProductItem(
    val productId: String,    // External product identifier
    val price: Double,        // Price of the product
    val quantity: Int,        // Quantity purchased
)
```

### Usage Examples

#### Basic Purchase Tracking

```kotlin
import com.firework.FireworkSdk
import com.firework.common.tracking.ProductItem

FireworkSdk.shopping.trackPurchase(
    orderId = "ORDER_123456",
    value = 99.99,
    currencyCode = "USD",
    countryCode = "US",
    additionalInfo = emptyMap(),
    products = null,
    shippingPrice = null,
    subtotal = null
)
```

#### Complete Purchase with Products

```kotlin
import com.firework.FireworkSdk
import com.firework.common.tracking.ProductItem
import java.util.Locale

// Create product items
val purchasedProducts = listOf(
    ProductItem(
        productId = "PROD_001",
        price = 29.99,
        quantity = 2
    ),
    ProductItem(
        productId = "PROD_002", 
        price = 39.99,
        quantity = 1
    )
)

// Track complete purchase
FireworkSdk.shopping.trackPurchase(
    orderId = "ORDER_789012",
    value = 109.97,
    currencyCode = "USD",
    countryCode = Locale.getDefault().country,
    additionalInfo = mapOf(
        "customer_id" to "CUST_12345",
        "campaign_id" to "SUMMER_SALE_2024",
        "payment_method" to "credit_card"
    ),
    products = purchasedProducts,
    shippingPrice = 9.99,
    subtotal = 99.98
)
```

#### Minimal Configuration

```kotlin
// Track with minimal required parameters
FireworkSdk.shopping.trackPurchase(
    orderId = "ORDER_MIN_001",
    value = null,         // Defaults to 0.0
    currencyCode = null,  // Defaults to "USD"
    countryCode = null,   // Defaults to device locale
    additionalInfo = emptyMap(),
    products = null,
    shippingPrice = null,
    subtotal = null
)
```

## Related Documentation

* [Product Hydration](/firework-for-developers/android-sdk/integration-guide/shoppable-videos/product-hydration.md) - Real-time product data integration
* [Analytics](/firework-for-developers/android-sdk/integration-guide/analytics.md) - Event tracking and analytics


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.firework.com/firework-for-developers/android-sdk/integration-guide/shoppable-videos.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
