> ## Documentation Index
> Fetch the complete documentation index at: https://dokumentation.websale.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Practical examples - Vouchers

> Practical examples for vouchers in WEBSALE: input form with maximumCount check, validation, redemption in checkout as well as typical frontend snippets.

This section contains practical examples for using vouchers in the checkout.

***

## Voucher input form with `maximumCount` check

The input form is only displayed as long as fewer vouchers have been redeemed than allowed. As soon as the upper limit is reached, the form disappears automatically.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $cActionVoucherAdd = $wsActions.create("VoucherAdd") }}
{{ include "components/errorAlert.htm" with $cAction = $cActionVoucherAdd, $cViewEachField = true }}

{{ if len($wsVoucher.vouchers) < $wsVoucher.maximumCount }}
    <form method="post" action="{{= $wsViews.current.url() }}" data-ws-ajax-form>
        <input type="hidden" name="wsReplaceIds" value="wsBasketWrapper,wsBasketEntries,wsBasketOffcanvasContent">
        <input type="hidden" name="wsact"    value="{{= $cActionVoucherAdd.id }}">
        <input type="hidden" name="wscsrf"   value="{{= $cActionVoucherAdd.csrf }}">
        <input type="hidden" name="wstarget" value="{{= $wsViews.current.url() }}">

        <input type="text" name="id" value="" placeholder="Enter voucher code">
        <button type="submit">Redeem</button>
    </form>
{{ /if }}
```

<Info>
  Error messages (e.g. "Minimum order value not reached") are defined and rendered via `components/errorAlert.htm`.
</Info>

***

## Displaying the list of redeemed vouchers

A separate small form with a unique ID is output for each voucher. Valid vouchers appear in green, invalid ones in red.

```text theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ if $wsVoucher.vouchers }}
    <p>Redeemed vouchers</p>

    {{ foreach $cVoucher in $wsVoucher.vouchers }}
        {{ var $cVoucherIsValid = $cVoucher.valid | ifNull(true) }}
        {{ var $cActionVoucherDelete = $wsActions.create("VoucherDelete") }}

        <form method="post"
              action="{{= $wsViews.viewUrl('basket.htm') }}"
              data-ws-ajax-form>
            <input type="hidden" name="wsReplaceIds" value="wsBasketWrapper,wsBasketEntries,wsBasketOffcanvasContent">
            <input type="hidden" name="id"       value="{{= $cVoucher.id }}">
            <input type="hidden" name="wsact"    value="{{= $cActionVoucherDelete.id }}">
            <input type="hidden" name="wscsrf"   value="{{= $cActionVoucherDelete.csrf }}">
            <input type="hidden" name="wstarget" value="{{= $wsViews.current.url() }}">

            <span>{{= $cVoucher.id }}</span>
            <button type="submit">Remove</button>
        </form>
    {{ /foreach }}
{{ /if }}
```

***

## Displaying all redeemed vouchers in the basket

In this example, all redeemed vouchers are shown in the basket. The customer can transparently see which codes are in the system and which one is currently being applied. Vouchers that have no effect are not added to the list and are indicated via an error message.

```text theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $cVoucherCount = 0 }}
{{ if $wsVoucher.vouchers }}
    {{ foreach $cVoucher in $wsVoucher.vouchers }}
        {{ $cVoucherCount = $cVoucherCount + 1 }}
        <tr>
            <td>
                <div>{{ if $cVoucherCount == 1 }}Voucher{{ else }}Additional voucher{{ /if }}</div>
                <div>{{= $cVoucher.id }}</div>
            </td>
            <td>
                -{{= $cVoucher.value | currency }}
            </td>
        </tr>
    {{ /foreach }}
{{ /if }}
```

***

## Free shipping method for voucher-only baskets

If a basket contains only (instant) vouchers, no physical shipping is required. For this case you can define a dedicated, always free shipping method under [`checkout.shippingMethod`](/en/konfiguration/checkout-bestellablauf#checkout-shippingmethod-versandarten): the `basicCost` scale sets the cost to `0` from a subtotal of `0`, and the validation [`shippingMethodValidation.productType`](/en/konfiguration/validierungs-und-prufservices) with `rule: deny` blocks the shipping method as soon as a regular product (product type `standard`) is in the basket — so it can only be selected for pure voucher baskets.

```json theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{
  "active": true,
  "basicCost": [
    {
      "cost": 0,
      "subtotal": 0
    }
  ],
  "description": "checkout.shippingMethod.INSTANT_VOUCHER.description",
  "group": null,
  "id": "INSTANT_VOUCHER",
  "image": "",
  "link": "",
  "name": "checkout.shippingMethod.INSTANT_VOUCHER.name",
  "orderText": "checkout.shippingMethod.INSTANT_VOUCHER.orderText",
  "taxable": true,
  "type": "standard",
  "validations": [
    {
      "options": {
        "rule": "deny",
        "ruleList": [
          "standard"
        ]
      },
      "service": "shippingMethodValidation.productType"
    }
  ],
  "weightCost": null
}
```

<Info>
  The value `standard` in the `ruleList` is the **value of the product type field** of the products (the product data field defined as product type via `content.usedFields`) — not to be confused with the shipping method's own `type: "standard"` parameter. Products for which the product type field is not set always pass the check.

  `name`, `description` and `orderText` refer to text snippets in this example, so the texts can be maintained per language via the text-snippet service.
</Info>
