# Product Purchase

> AddToCartForm, AddToCartButton, AttributeSelector, and BuyNowButton as one product purchase family.

<!-- Sources: src/Components/Woo/AddToCartForm/AddToCartForm.php; src/Components/Woo/AddToCartButton/AddToCartButton.php; src/Components/Woo/AttributeSelector/AttributeSelector.php; src/Components/Woo/BuyNowButton/BuyNowButton.php; client/src/domains/woo/components/add-to-cart-form.ts; client/src/domains/woo/components/add-to-cart-button.ts; client/src/domains/woo/components/attribute-selector.ts; client/src/domains/woo/components/buy-now-button.ts; client/src/domains/woo/helpers/variation-sync.ts; src/Testing/E2E/Woo/WooScenarioProvider.php; tests/e2e/components/woo/behavior.spec.ts -->

# Product Purchase

This family builds product add-to-cart UI. The parent is `AddToCartForm`; the usual children are `AttributeSelector`, `AddToCartButton`, and `BuyNowButton`.

## Components

| Component key | Role |
| --- | --- |
| `OmeWooAddToCartForm` | Owns the product ID, product type, variation ID, stock/availability flags, and submitted form state. |
| `OmeWooAttributeSelector` | Selects one variation attribute object from `omewoo.attributes`. |
| `OmeWooAddToCartButton` | Adds a product by itself in standalone mode, or submits the parent form in form-submit mode. |
| `OmeWooBuyNowButton` | Uses the parent form payload, adds the item, then redirects to checkout. |

## Authoring Structure

```text
AddToCartForm
  AttributeSelector loop over product attributes
  AddToCartButton with trigger=form_submit
  BuyNowButton
```

Use `AddToCartButton` as a standalone button only when it is outside an `AddToCartForm`.

## Quick Start

<CodeExample title="Variable product purchase form" language="jsx">
{`<OmeWooAddToCartForm product='{{"product_id":"{this.id}","product_type":"{this.omewoo.product.type}"}}'>
  {#slot default}
    {#loop this.omewoo.attributes as attribute}
      <OmeWooAttributeSelector target='{{"attribute":{attribute}}}' />
    {/loop}
    <OmeWooAddToCartButton behavior='{{"mode":"counter","trigger":"form_submit"}}' />
    <OmeWooBuyNowButton content='{{"label":"Buy now"}}' />
  {/slot}
</OmeWooAddToCartForm>`}
</CodeExample>

## AddToCartForm Props

| Prop | Meaning |
| --- | --- |
| `product.product_id` | Product ID. Use `{item.id}` in product loops and `{this.id}` on single product templates. |
| `product.product_type` | Product type, such as `simple` or `variable`. Use `omewoo.product.type` from product dynamic data. |
| `product.variation_id` | Optional fixed variation ID. Usually empty when selectors provide variation state. |
| `availability.unavailable` | Disables the form when authored or dynamic product availability says the product cannot be bought. |
| `availability.available` | Positive availability flag from dynamic data. |
| `availability.stock_status` | Stock status used by form sync and disabled state. |

Runtime blocks submit if the product is unavailable or if a required variable product option has not been selected.

## AddToCartButton Props

| Prop | Meaning |
| --- | --- |
| `behavior.mode` | `fixed` renders a normal button; `counter` renders quantity input plus button. |
| `behavior.trigger` | `standalone` sends its own Store API add request; `form_submit` submits the parent purchase form. |
| `behavior.product_id` | Required for standalone buttons. Ignored by form-submit buttons. |
| `quantity.default`, `min`, `max`, `step` | Quantity input defaults and bounds. Runtime clamps invalid values. |
| `content.label` | Button text. |
| `content.successMsg` | Temporary text after a successful add. |
| `accessibility.aria_label` | Button accessible label. |
| `accessibility.input_aria_label` | Quantity input accessible label in counter mode. |

E2E verifies standalone quick add updates `CartCount`, while a standalone-trigger button inside `AddToCartForm` does not accidentally submit the form.

## AttributeSelector Props

| Prop | Meaning |
| --- | --- |
| `target.attribute` | A full attribute object from `omewoo.attributes`. It contains key, label, required flag, and options. |
| `behavior.select_first_option` | Selects the first available option when nothing is selected. |
| `ui.mode` | `select`, `radio`, or `button-radio`. |
| `ui.aria_label` | Accessible selector name. |
| `ui.default_label` | Select trigger label before selection. |

Each option carries value, label, variation ID, availability, and stock status. Unavailable options are marked disabled and cannot be selected.

## BuyNowButton Props

| Prop | Meaning |
| --- | --- |
| `content.label` | Button text. |
| `accessibility.aria_label` | Optional accessible label. |
| `checkout.url` | Authored redirect target. If empty, runtime uses the localized Woo checkout URL. |

`BuyNowButton` must be inside an `AddToCartForm` because it serializes the form payload before redirecting.

## Scenario Coverage

| Scenario | Covered behavior |
| --- | --- |
| `cart_count_add_to_cart` | Standalone add updates count and success state. |
| `add_to_cart_button_inside_form` | Standalone add button inside a form is skipped by runtime. |
| `product_archive_simple_add` | Product loop card form submits and updates cart count. |
| `product_single_simple_add` | Quantity input clamps to max and submitted quantity updates cart count. |
| `variable_product_requires_variation` | Variable product blocks submit until a variation option is selected. |
| `variation_selector_button_radio` | Button-radio selector selects available option and rejects unavailable option. |
| `buy_now_redirect` | Buy-now adds to cart then redirects to checkout. |
| `out_of_stock_product_disabled` | Out-of-stock form state disables the purchase button. |
