# Product Actions API

Product actions allow you to programmatically retrieve product information.

## actions.product.getDetails()

Retrieve details of a loaded product.

### Signature

```typescript
getDetails(identifier: string): IBaseProductEventData
```

### Parameters

| Parameter    | Type   | Required | Description             |
|--------------|--------|----------|-------------------------|
| `identifier` | string | Yes      | Product UPC, SKU, or ID |

### Returns

```typescript
// Inherits all IProduct fields except `sizes` (see below for the overridden `sizes` shape)
interface IBaseProductEventData {
  identifier: string;
  name: string;
  description: string;
  priceInfo: IProductPriceInfo | null;  // { currency, minimum, average, maximum }
  selectedSizeId: string | null;
  selectedFulfillmentType: FulfillmentType;
  selectedFulfillmentId: string | null;
  productHasAvailability: boolean;
  fulfillmentHasAvailability: boolean;
  sizes: Record<string, IProductSizeEventData>;
  images: string[];
  brand: string;
  category: string;
  // ... additional IProduct fields
}
```

### Example

```javascript
const productData = window.LiquidCommerce.elements.actions.product.getDetails('00619947000020');

console.log(productData.name);        // "Premium Whiskey"
console.log(productData.priceInfo?.minimum / 100); // 49.99
console.log(productData.sizes[productData.selectedSizeId]?.size); // "750ml"
console.log(productData.selectedSizeId); // "size_123"
```

### Errors

**Throws `SDKError` if:**
- Identifier is empty or invalid
- Product has not been loaded yet (a non-existent product surfaces as this same error)

```javascript
try {
  const data = window.LiquidCommerce.elements.actions.product.getDetails('invalid_id');
} catch (error) {
  console.error('Product not found:', error.message);
}
```

### Use Cases

#### Display Product Info Elsewhere

```javascript
const product = window.LiquidCommerce.elements.actions.product.getDetails('00619947000020');

document.getElementById('product-name').textContent = product.name;
document.getElementById('product-price').textContent = `$${product.priceInfo?.minimum / 100}`;
```

#### Sync with Analytics

```javascript
window.addEventListener('lce:actions.product_loaded', () => {
  const product = window.LiquidCommerce.elements.actions.product.getDetails('00619947000020');
  
  gtag('event', 'view_item', {
    items: [{
      item_id: product.identifier,
      item_name: product.name,
      price: product.priceInfo?.minimum / 100,
      item_brand: product.brand
    }]
  });
});
```

#### Custom Product Comparison

```javascript
const product1 = window.LiquidCommerce.elements.actions.product.getDetails('00619947000020');
const product2 = window.LiquidCommerce.elements.actions.product.getDetails('08504405135');

if (product1.priceInfo?.minimum < product2.priceInfo?.minimum) {
  console.log(`${product1.name} is cheaper`);
}
```

---

## actions.product.getProductAvailabilityByState()

Check product availability across states.

### Signature

```typescript
getProductAvailabilityByState(
  identifiers: string[],
  state?: string
): Promise<IProductAvailabilityResponse>
```

### Parameters

| Parameter     | Type     | Required | Description                              |
|---------------|----------|----------|------------------------------------------|
| `identifiers` | string[] | Yes      | Array of product UPCs, SKUs, or IDs      |
| `state`       | string   | No       | Two-letter state code (e.g., `'NY'`)     |

### Returns

Resolves to an `IProductAvailabilityResponse`:

```typescript
interface IProductAvailabilityResponse {
  products: IProduct[];
  retailers: Record<string /* retailer ID */, IRetailer>;
}

interface IRetailer {
  id: string;
  name: string;
  address: IRetailerAddress;          // IAddressAddress & IAddressCoordinates
  addressFormatted: string;
  shippingFulfillment: IFulfillment | null;
  onDemandFulfillment: IFulfillment | null;
}

interface IFulfillment {
  id: string;
  type: FulfillmentType;              // 'shipping' | 'onDemand'
  doesAllowGiftCards: boolean;
  doesAllowPromos: boolean;
  expectation: string;
  engravingExpectation: string;
  fee: number;
  timezone: string;
  hourStatus: { isOpen: boolean; openTime: string; isClosed: boolean; closeTime: string };
}

interface IProduct {
  id: string; name: string; description: string; htmlDescription: string;
  images: string[]; brand: string; region: string; country: string;
  material: string; abv: string; proof: string; age: string; color: string;
  flavor: string; variety: string; appellation: string; vintage: string;
  tastingNotes: string; catPath: string; category: string; classification: string;
  type: string; subType: string; salsifyGrouping: string;
  priceInfo: IProductPriceInfo | null;  // { currency, minimum, average, maximum } — cents
  sizes: Record<string /* size ID */, IProductSize>;
}
```

### Example

```javascript
// Check availability in a specific state
const availabilityCA = await window.LiquidCommerce.elements.actions.product
  .getProductAvailabilityByState(['00619947000020', '08504405135'], 'CA');

// Omit the state to query without a state filter
// (state: undefined is sent — the SDK does NOT derive it from the stored address)
const availability = await window.LiquidCommerce.elements.actions.product
  .getProductAvailabilityByState(['00619947000020']);
```

### Errors

**Throws `SDKError` if:**
- No identifiers provided or array is empty
- API request fails

---

## Notes

- Product must be injected and loaded before calling `getDetails()`
- `getDetails()` is synchronous - returns immediately
- `getProductAvailabilityByState()` is async - returns a Promise
- Prices are always in cents (divide by 100 for dollars)
- Selected values reflect current user selection in the component

## See Also

- [Product Component Guide](../../guides/product-component.md)
- [Product Events](../../guides/events.md#product-events)
