Interchange Ecommerce Functions

Table of Contents


Interchange has a completely flexible order basket and checkout scheme. The foundation demo presents a common use of this process, in the directory pages/ord -- the files are:

    basket.html      The order basket displayed by default
    checkout.html    The form where the customer enters their billing
                     and shipping info
    receipt.html     The receipt displayed to the customer
    report           The order report mailed to you
    mail_receipt     The customer's email copy (if requested)

It is not strictly necessary to display an order basket when an item is ordered. If you specify a different page to be displayed that is fine, but most customers will be confused if you don't give them an indication that the order operation has succeeded.

Any order basket is an HTML FORM. It will have a number of variables on it. At the minimum it must have an [item-list] to loop through the items, and the quantity of each item must be set in some place on that form. Any valid Interchange tags may be used on the page, and you may use multiple item lists if necessary.

1.1. How to order an item

Interchange can either use a form-based order or a link-based order to place an item in the shopping cart. The link-based order uses the special [order item-code] tag:

[order code]

            [order code="sku" quantity="n"* href="page"* cart="cartname"* base="table"*]
            * = optional parameters
      Order a [order TK112]Toaster</a> today.
      Order a [page order TK112]Toaster</A> today.
      Order a <A HREF="[area order TK112]" TARGET=newframe>Toaster</A> today.


To order with a form, you set the form variable mv_order_item to the item-code/SKU and use the refresh action:

  <FORM ACTION="[process-target]" METHOD=POST>
  <INPUT TYPE=hidden  NAME="mv_todo"        VALUE="refresh">
  <INPUT TYPE=hidden  NAME="mv_order_item"  VALUE="TK112">

  Order <INPUT NAME="mv_order_quantity" SIZE=3 VALUE=1> toaster

  <INPUT TYPE=submit VALUE="Order!">

You may batch select whole groups of items:

  <FORM ACTION="[process-target]" METHOD=POST>
  <INPUT TYPE=hidden  NAME="mv_todo"        VALUE="refresh">

  <INPUT TYPE=hidden  NAME="mv_order_item"  VALUE="TK112">
  <INPUT NAME="mv_order_quantity" SIZE=3> Standard Toaster

  <INPUT TYPE=hidden  NAME="mv_order_item"  VALUE="TK200">
  <INPUT NAME="mv_order_quantity" SIZE=3> Super Toaster

  <INPUT TYPE=submit VALUE="Order!">

Items that have a quantity of zero (or blank) will be skipped, and only items with a positive quantity will be placed in the basket.

You may also specify attributes like size or color at time of order (see How to set up an order button).

1.2. How to set up an order link

On a product display page, use:

    [order 00-0011]Order the Mona Lisa</a>

If coming from a search results or on-the-fly page, you may use the generated [item-code] thusly:

    [order [item-code]]Order [item-field name]</a>

Bear in mind that if you have not reached the page via a search or on-the-fly operation, [item-code] means nothing and will cause an error.

1.3. How to set up an order button

Interchange can order via form submission as well. This allows you to order a product (or group of products) via a form button. In its simplest form, it is:

    <FORM ACTION="[process-target]" METHOD=POST>
    <INPUT TYPE=hidden NAME=mv_todo VALUE=refresh>
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="00-0011">
    <INPUT TYPE=submit VALUE="Order the Mona Lisa">

The default quantity is one. An initial quantity may be set by the user by adding an mv_order_quantity variable:

     Number to order:<INPUT TYPE=text NAME=mv_order_quantity VALUE="1">

You can order multiple items by stacking the variables:

    <FORM ACTION="[process-target]" METHOD=POST>
    <INPUT TYPE=hidden NAME=mv_todo VALUE=refresh>
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="00-0011">
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="00-0011a">
    <INPUT TYPE=submit VALUE="Order the Mona Lisa with frame">

Initial size or color may be set as well, provided UseModifier is set up properly:

    <INPUT TYPE=hidden NAME=mv_order_size VALUE="L">

If the order is coming from a generated flypage, loop list, or search results page, you can get a canned select box from the [item-accessories size] or [item-accessories size] tag. See Item Attributes.

1.4. How to set up an on-the-fly item

If you enable the catalog directive OnFly, setting it to the name of a subroutine (or possibly a UserTag) that can handle its calls, then Interchange will add items to the basket that are not in the product database. Interchange supplies an internal onfly subroutine, which will work according to the examples given below.

In catalog.cfg:

    OnFly  onfly

If your item code is not to be named mv_order_item then you must perform a rename in the Autoload routine.

A basic link can be generated like:

    <a href="[area form="
            mv_order_fly=description=An on-the-fly item|price=100.01
    "]">Order item 000101</a>

The form parameter value mv_order_fly can contain any number of fields which will set corresponding parameters in the item attributes. The fields are separated by the pipe (|) character and contain value-parameter pairs separated by an = sign. (These are URL-encoded by the [area ...] or [page ...] tag, of course.) You can set a size, color, or any other parameter.

The special attribute mv_price can be used in conjunction with the CommonAdjust atom $ to set the price for checkout and display.

The [item-list] sub-tag [item-description], when used with an item-list, will use the item attribute description to display in the basket. Note that [item-field description] or [item-data products description] will NOT work, as both of these tags reference an actual field value for a record in the products table - not applicable for on-the-fly items. Similarly, an attempt to generate a flypage for an on-the-fly item ([page 000101], for example) will fail, resulting in the display of the SpecialPage missing.

If you wish to set up a UserTag to process on-the-fly items, it should accept a call of

    usertag(mv_item_code, mv_item_quantity, mv_order_fly)

The mv_item_code and mv_order_fly parameters are required to trigger Interchange's add_item routine (along with mv_todo=refresh to set the action).

The item will always act as if SeparateItems or mv_separate_items is set.

Multiple items can be ordered at once by stacking the variables. If there is only one mv_order_item instance, however, you can stack the mv_order_fly variable so that all are concatenated together as with the | symbol. So the above example could be done as:

    [area form="
            mv_order_fly=description=An on-the-fly item

Multiple items would need multiple instances of mv_order_item with a corresponding mv_order_fly for each mv_order_item. You can order both 000101 and 000101 as follows:

    [area form="

        mv_order_fly=description=An on-the-fly item|price=100.00

        mv_order_fly=description=Another on-the-fly item|price=200.00

The following two forms correspond to the above two examples, in order, with the slight refinement of adding a quantity:

  <FORM ACTION="[area process]" METHOD=POST>
        <INPUT TYPE=hidden NAME=mv_todo VALUE="refresh">
        <INPUT TYPE=hidden NAME=mv_order_item VALUE="000101">
        Qty: <INPUT SIZE=2 NAME=mv_order_quantity VALUE="1">
        <INPUT TYPE=hidden NAME=mv_order_fly
                VALUE="description=An on-the-fly item|price=100.00">
        <INPUT TYPE=submit VALUE="Order button">

   <FORM ACTION="[area process]" METHOD=POST>
        <INPUT TYPE=hidden NAME=mv_todo VALUE="refresh">
        <INPUT TYPE=hidden NAME=mv_order_item VALUE="000101">
        Qty: <INPUT SIZE=2 NAME=mv_order_quantity VALUE="1"><BR>
        <INPUT TYPE=hidden NAME=mv_order_fly
            VALUE="description=An on-the-fly item|price=100.00">
        <INPUT TYPE=hidden NAME=mv_order_item VALUE="000102">
        Qty: <INPUT SIZE=2 NAME=mv_order_quantity VALUE="1"><BR>
        <INPUT TYPE=hidden NAME=mv_order_fly
            VALUE="description=Another on-the-fly item|price=200.00">
        <INPUT TYPE=submit VALUE="Order two different with a button">

1.5. Order Groups

Interchange allows you to group items together, making a master item and sub-items. This can be used to delete accessories or options when the master item is deleted. In its simplest form, you order just one master item and all subsequent items are sub-items.

    <FORM ACTION="[process-target]" METHOD=POST>
    <INPUT TYPE=hidden NAME=mv_todo VALUE=refresh>
    <INPUT TYPE=hidden NAME=mv_order_group VALUE="1">
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="00-0011">
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="00-0011a">
    <INPUT TYPE=submit VALUE="Order the Mona Lisa with frame">

If you wish to stack more than one master item, then you must define mv_order_group for all items, with either a 1 value (master) or 0 value (sub-item). A master owns all subsequent sub-items until the next master is defined.

    <FORM ACTION="[process-target]" METHOD=POST>
    <INPUT TYPE=hidden NAME=mv_todo VALUE=refresh>
    <INPUT TYPE=hidden NAME=mv_order_group VALUE="1">
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="00-0011">
    <INPUT TYPE=hidden NAME=mv_order_group VALUE="0">
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="00-0011a">
    <INPUT TYPE=hidden NAME=mv_order_group VALUE="1">
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="19-202">
    <INPUT TYPE=hidden NAME=mv_order_group VALUE="0">
    <INPUT TYPE=hidden NAME=mv_order_item VALUE="99-102">
    <INPUT TYPE=submit VALUE="Order items">

When the master item 00-0011 is deleted from the basket, 00-0011a will be deleted as well. And when 19-202 is deleted, then 99-102 will be deleted from the basket.

NOTE: Use of checkboxes for this type of thing can be hazardous, as they do not pass a value when unchecked. It is preferable to use radio groups or select/drop-down widgets. If you must use checkboxes, be sure to explicitly clear mv_order_group and mv_order_item somewhere on the page which contains the form:

    [value name=mv_order_group set='']
    [value name=mv_order_item set='']

The attributes mv_mi and mv_si are set to the group and sub-item status of each item. The group, contained in the attribute mv_mi, is a meaningless yet unique integer. All items in a group will have the same value of mv_mi. The attribute mv_si is set to 0 if the item is a master item, and 1 if it is a sub-item.

1.6. Basket display

The basket page(s) are where the items are tracked and adjusted by the customer. It is possible to have an unlimited number of basket pages. It is also possible to have multiple shopping carts, as in buy or sell. This allows a basket/checkout type of ordering scheme, with custom order pages for items which have many accessories.

The name of the page to display can be configured in several ways:

  1. Set the SpecialPage order to the page to display when an item is ordered.
  2. Use the [order code=item page=page_name] Order it! </a> form of order tag to specify an arbitrary order page for an item.
  3. If already on an order page, set the mv_orderpage, mv_nextpage, mv_successpage, or mv_failpage variables.

The following variables can be used to control cart selection and page display:







1.7. Multiple Shopping Carts

Interchange allows you to define and maintain multiple shopping carts. One shopping cart -- main, by name -- is defined when the user session starts. If the user orders item M1212 with the following tag:

    [order code=M1212 cart=layaway] Order this item! </a>

the order will be placed in the cart named layaway. However, by default you won't see the just-ordered item on the basket page. That is because the default shopping basket displays the contents of the 'main' cart only. So copy the default basket page (pages/ord/basket.html in the demo) to a new file, insert a [cart layaway] tag, and specify it as the target page in your [order] tag:

    [order code=M1212 cart=layaway page=ord/lay_basket] Order this item! </a>

Now the contents of the layaway cart will be displayed. Most of the ITL tags that are fundamental to cart display accept a 'cartname' option, allowing you to specify which cart to be used:

[cart cartname]

[item-list cartname]...[/item-list]

[nitems cartname]

[subtotal cartname]

[shipping cartname], [handling cartname], [salestax cartname], [total-cost cartname]

You can also order items from a form, using the mv_order_item, mv_cartname, and optional mv_order_quantity variables.

 <input type=checkbox name="mv_order_item" value="M3243"> Item M3243
 <input name="mv_order_quantity" value="1"> Quantity
 <input type=hidden name="mv_cartname" value="layaway">
 <input type=hidden name="mv_doit" value="refresh">
 <input type=submit name="mv_junk" value="Place on Layaway Now!">

If you need to utilize an alternative item price in conjunction with the use of a custom cart, see the section on PRODUCT PRICING for pricing methods and strategies.


Interchange maintains a price in its database for every product. The price field is the one required field in the product database -- it is necessary to build the price routines.

For speed, Interchange builds the code that is used to determine a product's price at catalog configuration time. If you choose to change a directive that affects product pricing you must reconfigure the catalog.

2.1. Simple pricing

The simplest method is flat pricing based on a fixed value in the products database. If you put that price in a field named price, you don't need to do more. If you want to change pricing based on quantity, size, color or other factors read on.

2.2. Price Maintenance with CommonAdjust

A flexible chained pricing scheme is available when the CommonAdjust directive is set.

We talk below about a CommonAdjust string; it will be defined in due time.

A few rules about CommonAdjust, all assuming the PriceField directive is set to price:


If CommonAdjust is set to any value, a valid CommonAdjust string or not, extended price adjustments are enabled. It may also hold the default pricing scheme.


The price field may also hold a CommonAdjust string. It takes precedence over the default.


If the value of the CommonAdjust directive is set to a CommonAdjust string, and the price field is empty or specifically 0, then it will be used to set the price of the items.


If no CommonAdjust strings are found, then the price will be 0, subject to any later application of discounts.


If another CommonAdjust string is found as the result of an operation, it will be re-parsed and the result applied. Chaining is retained; a fallback may be passed and will take effect.

Prices may be adjusted in several ways, and the individual actions are referred to below as atoms. Price atoms may be final, chained, or fallback. A final price atom is always applied if it does not evaluate to zero. A chained price atom is subject to further adjustment. A fallback price atom is skipped if a previous chained price was not zero.

Atoms are separated by whitespace, and may be quoted (although there should not normally be whitespace in a setting). A chained item ends with a comma. A fallback item has a leading semi-colon. Final atoms have no comma appended or semi-colon prepended.

A settor is the means by which the price is set. There are eight different types of price settors. All settors can then yield another CommonAdjust string.

It is quite possible to create endless loops, so the maximum number of initial CommonAdjust strings is set to 16, and there may be only 32 iterations by default before the price will return zero on an error. (The maximum iterations is specified with the Limit directive.)

NOTE: Common needs are easily shown but not so easily explained; skip to the examples in the reference below if your vision starts to blur when reading the next section. 8-)

USAGE: Optional items below have asterisks appended. The asterisk should not be used in the actual string. Optional base or table always defaults to the active products database table. The optional key defaults to the item code except in a special case for the attribute-based lookup. The field name is not optional except in the case of an attribute lookup.

N.NN or -N.NN







      $Vend::Interpolate::item          is the current item
      $Vend::Interpolate::item->{code}  gives key for current item
      $Vend::Interpolate::item->{size}  gives size for current item (if there)
      $Vend::Interpolate::item->{mv_ib} gives database ordered from

[valid Interchange tags]




( settor )

2.3. CommonAdjust Examples

Most examples below use an outboard database table named pricing, but any valid table including the products table can be used. We will refer to this pricing table:

  code    common  q1     q5     q10    XL    S      red
  99-102          10     9      8      1     -0.50  0.75
  00-343                               2
  red      0.75

The simplest case is a straight lookup on an attribute; size in this case.

  10.00, ==size:pricing

With this value in the price field, a base price of 10.00 will be adjusted with the value of the size attribute. If size for the item 99-102 is set to XL then 1.00 will be added for a total price of 11.00; if it is S then .50 will be subtracted for a total price of 9.50; for any other value of size no further adjustment would be made. 00-343 would be adjusted up 2.00 only for XL.

  10.00, ==size:pricing, ==color:pricing

This is the same as above, except both size and color are adjusted for. A color value of red for item code 99-102 would add 0.75 to the price. For 00-343 it would have no effect.

  10.00, ==size:pricing, ==color:pricing:common

Here price is set based on a common column, keyed by the value of the color attribute. Any item with a color value of red would have 0.75 added to the base price.

  pricing:q1,q5,q10:, ;10.00, ==size:pricing, ==color:pricing:common

Here is a quantity price lookup, with a fallback price setting. If there is a valid price found at the quantity of 1, 5, or 10, depending on item quantity, then it will be used. The fallback of 10.00 only applies if no non-zero/non-blank price was found at the quantity lookup. In either case, size/color adjustment is applied.

  pricing:q1,q5,q10:, ;10.00 ==size:pricing, ==color:pricing:common

Removing the comma from the end of the fallback string stops color/size lookup if it reaches that point. If a quantity price was found, then size and color are chained.

  pricing:q1,q5,q10:, ;products:list_price, ==size:pricing, ==color:pricing

The value of the database column list_price is used as a fallback instead of the fixed 10.00 value. The above value might be a nice one to use as the default for a typical retail catalog that has items with colors and sizes.

2.4. PriceBreaks, discounts, and PriceAdjustment

There are several ways that Interchange can modify the price of a product during normal catalog operation. Several of them require that the pricing.asc file be present, and that you define a pricing database. You do that by placing the following directive in catalog.cfg:

  Database  pricing pricing.asc 1

NOTE: PriceAdjustment is slightly deprecated by CommonAdjust, but will remain in use at least through the end of Version 3 of Interchange.

Configurable directives and tags with regard to pricing:

For example, if you decided to adjust the price of T-shirt part number 99-102 up 1.00 when the size is extra large and down 1.00 when the size is small, you would have the following directives defined in <catalog.cfg>:

  Database          pricing pricing.asc 1
  UseModifier       size
  PriceAdjustment   size

To enable automatic modifier handling, you define a size field in products.txt:

  code    description   price    size
  99-102  T-Shirt       10.00    S=Small, M=Medium, L=Large*, XL=Extra Large

You would place the proper tag within your [item-list] on the shopping-basket or order page:

    [item-accessories size]

In the pricing.asc database source, you would need:

  code      S       XL
  99-102    -1.00   1.00

If you want to assign a price based on the option, precede the number with an equals sign:

  code    S       M       L       XL
  99-102  =9.00   =10     =10     =11

IMPORTANT NOTE: Price adjustments occur AFTER quantity price breaks, so the above would negate anything set with the PriceBreaks directive/option.

Numbers that begin with an equals sign (=) are used as absolute prices and are interpolated for Interchange tags first, so you can use subroutines to set the price. To facilitate coordination with the subroutine, the session variables item_code and item_quantity are set to the code and quantity of the item being evaluated. They would be accessed in a global subroutine with $Vend::Session->{item_code} and $Vend::Session->{item_quantity}.

The pricing information must always come from a database because of security.

2.5. Item Attributes

Interchange allows item attributes to be set for each ordered item. This allows a size, color, or other modifier to be attached to a common part number. If multiple attributes are set, then they should be separated by commas. Previous attribute values can be saved by means of a hidden field on a form, and multiple attributes for each item can be stacked on top of each other.

The configuration file directive UseModifier is used to set the name of the modifier or modifiers. For example

    UseModifier        size,color

will attach both a size and color attribute to each item code that is ordered.

IMPORTANT NOTE: You may not use the following names for attributes:

    item  group  quantity  code  mv_ib  mv_mi  mv_si

You can also set it in scratch with the mv_UseModifier scratch variable -- [set mv_UseModifier]size color[/set] has the same effect as above. This allows multiple options to be set for products. Whichever one is in effect at order time will be used. Be careful, you cannot set it more than once on the same page. Setting the mv_separate_items or global directive SeparateItems places each ordered item on a separate line, simplifying attribute handling. The scratch setting for mv_separate_items has the same effect.

The modifier value is accessed in the [item-list] loop with the [item-modifier attribute] tag, and form input fields are placed with the [modifier-name attribute] tag. This is similar to the way that quantity is handled, except that attributes can be "stacked" by setting multiple values in an input form.

You cannot define a modifier name of code or quantity, as they are already used. You must be sure that no fields in your forms have digits appended to their names if the variable is the same name as the attribute name you select, as the [modifier-name size] variables will be placed in the user session as the form variables size0, size1, size2, etc.

You can use the [loop arg="item item item"] list to reference multiple display or selection fields for modifiers, or you can use the built-in [PREFIX-accessories ...] tags available in most Interchange list operations. The modifier value can then be used to select data from an arbitrary database for attribute selection and display.

Below is a fragment from a shopping basket display form which shows a selectable size with "sticky" setting. Note that this would always be contained within the [item_list] [/item-list] pair.

    <SELECT NAME="[modifier-name size]">
    <OPTION  [selected [modifier-name size] S]> S
    <OPTION  [selected [modifier-name size] M]> M
    <OPTION  [selected [modifier-name size] L]> L
    <OPTION [selected [modifier-name size] XL]> XL

It could just as easily be done with a radio button group combined with the [checked ...] tag.

Interchange will automatically generate the above select box when the [accessories <code size]> or [item-accessories size] tags are called. They have the syntax:

   [item_accessories attribute*, type*, field*, database*, name*, outboard*]

   [accessories code attribute*, type*, field*, database*, name*, outboard*]




      select          Builds a dropdown <SELECT> menu for the attribute.
                      NOTE: This is the default.
      multiple        Builds a multiple dropdown <SELECT> menu for the
                      attribute.  The size is equal to the number of
                      option choices.
      display         Shows the label text for *only the selected option*.
      show            Shows the option choices (no labels) for the option.
      radio           Builds a radio box group for the item, with spaces
                      separating the elements.
      radio nbsp      Builds a radio box group for the item, with &nbsp;
                      separating the elements.
      radio left n    Builds a radio box group for the item, inside a
                      table, with the checkbox on the left side. If "n"
                      is present and is a digit from 2 to 9, it will align
                      the options in that many columns.
      radio right n   Builds a radio box group for the item, inside a
                      table, with the checkbox on the right side. If "n"
                      is present and is a digit from 2 to 9, it will align
                      the options in that many columns.
      check           Builds a checkbox group for the item, with spaces
                      separating the elements.
      check nbsp      Builds a checkbox group for the item, with &nbsp;
                      separating the elements.
      check left n    Builds a checkbox group for the item, inside a
                      table, with the checkbox on the left side. If "n"
                      is present and is a digit from 2 to 9, it will align
                      the options in that many columns.
      check right n   Builds a checkbox group for the item, inside a
                      table, with the checkbox on the right side. If "n"
                      is present and is a digit from 2 to 9, it will align
                      the options in that many columns.





When called with an attribute, the database is consulted and looks for a comma-separated list of attribute options. They take the form:

    name=Label Text, name=Label Text*

The label text is optional -- if none is given, the name will be used.

If an asterisk is the last character of the label text, the item is the default selection. If no default is specified, the first will be the default. An example:

    [item_accessories color]

This will search the product database for a field named "color". If an entry "beige=Almond, gold=Harvest Gold, White*, green=Avocado" is found, a select box like this will be built:

    <SELECT NAME="mv_order_color">
    <OPTION VALUE="beige">Almond
    <OPTION VALUE="gold">Harvest Gold
    <OPTION VALUE="green">Avocado

In combination with the mv_order_item and mv_order_quantity variables this can be used to allow entry of an attribute at time of order.

If used in an item list, and the user has changed the value, the generated select box will automatically retain the current value the user has selected.

The value can then be displayed with [item-modifier size] on the order report, order receipt, or any other page containing an [item-list].

2.6. Product Discounts

Product discounts can be set upon display of any page. The discounts apply only to the customer receiving them, and are of one of three types:

    1. A discount for one particular item code (key is the item-code)
    2. A discount applying to all item codes (key is ALL_ITEMS)
    3. A discount for an individual line item (set the mv_discount attribute
       with embedded Perl)
    4. A discount applied after all items are totaled
       (key is ENTIRE_ORDER)

The discounts are specified via a formula. The formula is scanned for the variables $q and $s, which are substituted for with the item quantity and subtotal respectively. The variable $s is saved between iterations, so the discounts are cumulative. In the case of the item and all items discount, the formula must evaluate to a new subtotal for all items of that code that are ordered. The discount for the entire order is applied to the entire order, and would normally be a monetary amount to subtract or a flat percentage discount.

Discounts are applied to the effective price of the product, including any quantity discounts or price adjustments.

To apply a straight 20% discount to all items:

    [discount ALL_ITEMS] $s * .8 [/discount]

or with named attributes:

    [discount code=ALL_ITEMS] $s * .8 [/discount]

To take 25% off of only item 00-342:

    [discount 00-342] $s * .75 [/discount]

To subtract $5.00 from the customer's order:

    [discount ENTIRE_ORDER] $s - 5 [/discount]

To reset a discount, set it to the empty string:

    [discount ALL_ITEMS][/discount]

Perl code can be used to apply the discounts, and variables are saved between items and are shared with the [calc] tag. This example gives 10% off if two items are ordered, with 5% more for each additional up to a maximum of 30% discount:

            $totalq{"[item-code]"} += [item-quantity];
            return '';

        [discount code="[item-code]"]
            return ($s)       if $totalq{"[item-code]"} == 1;
            return ($s * .70) if $totalq{"[item-code]"} > 6;
            return ($s * ( 1 - 0.05 * $totalq{"[item-code]"} ));

Here is an example of a special discount for item code 00-343 which prices the second one ordered at 1 cent:

    [discount 00-343]
    return $s if $q == 1;
    my $p = $s/$q;
    my $t = ($q - 1) * $p;
    $t .= 0.01;
    return $t;

If you want to display the discount amount, use the [item-discount] tag.

    Discount for [item-code]: [item-discount]

Finally, if you want to display the discounted subtotal, you need to use the [calc] capability:

    Discounted subtotal for [item-code]: [currency][calc]
                                            [item-price] * [item-quantity]

3. Taxing

Interchange allows taxing in a number of ways.

Simple salestax.asc table

The SalesTax directive is set to a form field or fields for user input, and those form fields are used look up the tax rate in salestax.asc.

Fly tax

Another simple tax method. A series of Interchange Variable settings are read to develop a salestax rate for one or a few localities, usually a state in the US.

Salestax multi -- VAT taxing

The country and state tables are used to develop complex VAT or salestax rate calculations based on country and state, possibly with different rates based on product type.

Levies -- multiple levels of tax

Using the Levies setting and the Levy structure, any or all of the above methods is used to implement one or more taxes.

3.1. Sales Tax -- simple salestax.asc table

Interchange allows calculation of sales tax on a straight percentage basis, with certain items allowed to be tax-exempt. To enable this feature, the directive SalesTax is initialized with the name of a field (or fields) on the order form. Commonly, this is zipcode and/or state:

    SalesTax    zip,state

This being done, Interchange assumes the presence of a file salestax.asc, which contains a database with the percentages. Each line of salestax.asc should be a code (again, usually a five-digit zip or a two letter state) followed by a tab, then a percentage. Example:

        DEFAULT 0.0
    45056   .0525
    61821   .0725
    61801   .075
    IL      .0625
    OH      .0525
    VAT     .15
    WA      .08

Based on the user's entry of information in the order form, Interchange will look up (for our example SalesTax directive) first the zip, then the state, and apply the percentage to the SUBTOTAL of the order. The subtotal will include any taxable items, and will also include the shipping cost if the state/zip is included in the TaxShipping directive. It will add the percentage, then make that available with the [salestax] tag for display on the order form. If no match is found, the entry DEFAULT is applied -- it is normally zero.

If business is being done on a national basis, it is now common to have to collect sales tax for multiple states. If you are doing so, it is possible to subscribe to a service which issues regular updates of the sales tax percentages -- usually by quarterly or monthly subscription. Such a database should be easily converted to Interchange format -- but some systems are rather convoluted, and it will be well to check and see if the program can export to a flat ASCII file format based on zip code.

If some items are not taxable, then you must set up a field in your database which indicates that. You then place the name of that field in the NonTaxableField directive. If the field for that item evaluates true on a yes-no basis (i.e. is set to yes, y, 1, or the like), sales tax will not be applied to the item. If it evaluates false, it will be taxed.

If your state taxes shipping, use the TaxShipping directive. Utah and Nevada are known to tax shipping -- there may be others.

If you want to set a fixed tax for all orders, as might occur for VAT in some countries, just set the SalesTax directive to a value like tax_code, and define a variable in the user session to reflect the proper entry in the salestax.asc file. To set it to 15% with the above salestax.asc file, you would put in a form:

    <INPUT TYPE=hidden NAME=tax_code VALUE="VAT">

or to do it without submitting a form:

    [perl] $Values->{tax_code} = 'VAT'; return; [/perl]

3.2. Fly tax

The [fly-tax] tag is placed in the DEFAULT setting of salestax.asc, and the variables TAXAREA, TAXRATE, and TAXSHIPPING are used to build the rates.


A space-separated or comma-seperated list of states to apply tax to. Not needed for anything in the calculation, it is used to build the UI list of states to tax.


An Interchange accessory-list style of value, with the format


where XX is the two-letter state code and N.NN is the tax rate in percent. To apply a tax of 7.25% for Illinois and 5.5% for Nevada, you would use:

IL=7.25, NV=5.5


A space- or comma-separated list of states where shipping is taxed. For the above example, if Nevada taxed shipping and Illinois did not, you would make TAXSHIPPING equal to "NV".

The Salestax Directive

To set the field that is used for the state code, you use the standard Interchange SalesTax directive. It would almost always be set to state.

3.3. Salestax "multi" -- VAT taxing

If the SalesTax directive is set to "multi", then the type of tax is read from the country table. To see the tax type in force for the UK, you can place in a page:

        [data table=country col=tax key="UK"].
  MV_COUNTRY_TABLE      Table for country info (default "country")
  MV_COUNTRY_FIELD      Form field determining country (default "country")
  MV_COUNTRY_TAX_FIELD  Table column for country-wide tax (default "tax")
  MV_STATE_TABLE        Table for state/province info (default "state")
  MV_STATE_FIELD        Form field determining state/province (default "state")
  MV_STATE_TAX_FIELD    Table column for state-wide tax (default "tax")
  MV_TAX_TYPE_FIELD     Table column enumerating tax types (default "tax_type")
  MV_TAX_CATEGORY_FIELD Table column for product type (default tax_category)

Below, we refer to the tables, columns, and fields by their default names.

The first lookup is done in table country based on the user input of country (i.e. [value country]). The tax field is read and one of the following is done:

1. If no string is found, tax returns 0.

2. If string "simple:XX" is found, uses [fly-tax] with the area specifed in XX.

3. If string "state" is found, does a re-lookup with

     select tax from state where country = country and state = state

and value is applied as below.

4. If just digits are found, rate applied directly -- i.e. "0.05"

5. If N.NN% is found, applied as percentage.

6. If category = N.NN%, default = N.NN% is found, the tax_category field in the products table is used to determine tax basis. If no tax_category is found for the product, default rate is used.

This product data

    sku      price     tax_category
    os28003  10.00     tools
    os28004  20.00     food

with this country and state data:

    code     name     tax
    US       U.S.A.   state
    JP       Japan    tools=10%, default=15%

    code   country   state   name      tax
    0001   US        IL      Illinois  6.5%
    0002   US        OH      Ohio      default = 5.5%, food = 1%
    0003   US        AZ      Arizona

Will yield tax for one each of os28003 and os28004 of:

    Japan   $4.00
    US/IL   $1.95
    US/OH   $0.75
    US/AZ   $0.00


4.1. Advanced Multi-level Order Pages

An unlimited number of order checking profiles can be defined with the OrderProfile directive, or by defining order profiles in scratch variables. This allows a multi-level ordering process, with checking for format and validity at every stage.

To custom-configure the error message, place it after the format check requirement.

Specifications take the form of an order page variable (like name or address), followed by an equals sign and one of five check types:















            foo=regex ^bar
       foo=regex ^bar "You must have bar at the beginning of this"
       foo=regex !^bar "You may not have bar at the beginning!"


       foo=length 4-10


       foo=unique userdb Sorry, that username is already taken


       foo=filter entities Sorry, no HTML allowed
            foo=filter lower Sorry, no uppercase characters

Also, there are pragmas that can be used to change behavior:



        # Checks credit card number and destroys number after encryption
        # The charge operation can never work
        &charge=custom authorizenet
        # Checks credit card number and keeps number after encryption
        # The charge operation can now work
        &credit_card=standard keep
        &charge=custom authorizenet







        # Success :)
        &return 1
        # Failure :\
        &return 0





As an added measure of control, the specification is evaluated for the special Interchange tags to provide conditional setting of order parameters. With the [perl] [/perl] capability, quite complex checks can be done. Also, the name of the page to be displayed on an error can be set in the mv_failpage variable.

The following file specifies a simple check of formatted parameters:

 name=required You must give us your name.
 address=required Oops! No address.
 phone_day=phone_us XXX-XXX-XXXX phone-number for US or Canada
 email=email Email address missing the domain?
 &set=mv_email [value email]
 &set=mv_successpage ord/shipping

The profile above only performs the &set directives if all of the previous checks have passed -- the &fatal=yes will stop processing after the check of the email address if any of the previous checks failed.

If you want to place multiple order profiles in the same file, separate them with __END__, which must be on a line by itself.

User-defined check routines can be defined in a GlobalSub:

    GlobalSub <<EOF
    sub set_up_extra_check {
        BEGIN {
            package Vend::Order;
            sub _pt_postcode {
                # $ref is to Vend::Session->{'values'} hash
                # $var is the passed name of the variable
                # $val is current value of checked variable
                my($ref, $var, $val) = @_;

                if ($ref->{country} =~ /^(PT|portugal)$/i) {
                    return $val =~ /^\d\d\d\d$/ ?
                        (1, $var, '') : (undef, $var, "not a Portugese postal code");
                else {
                    return (1, $var, '');

Now you can specify in an order profile:


Very elaborate checks are possible. There must be an underscore preceding the routine name. The return value of the subroutine should be a three-element array, consisting of:

  1. the pass/fail ('1' or 'undef') status of the check;
  2. the name of the variable which was checked;
  3. a standard error message for the failure, in case a custom one has not been specified in the order profile.

The latter two elements are used by the [error] tag for on-screen display of form errors. The checkout page of the Foundation demo includes examples of this.

4.2. Simple Order Report File

The simple order report file, "report", defines the layout of the order report which gets mailed on the completion of the order. For example,

              Order Date: $date

                    Name: $name
           Email address: $email

        Shipping address: $addr
        Town, State, Zip: $town, $state $zip
                 Country: $country

Any input field from the order page can be included using the dollar sign notation.

Interchange defines some values for use in the search form -- they begin with mv_ and are automatically ignored.

4.3. Fully-configurable Order Reports

You can specify a fully-configurable order report by setting the hidden field "mv_order_report" to a legal Interchange page. This page will be interpolated with all Interchange tags before sending by email. The order number as set by backend ordering is in the variable mv_order_number, and available for your use.

You could if you wish include HTML in the file, which will be interpreted by many mailers, but you can choose to use standard ASCII format. An example report is provided in the demo file <pages/ord/report.html>.

4.4. Order Receipts

The file can of course be configured with all Interchange tags, and will be interpolated for the ordered items before they are deleted from the user session. You can set the default receipt with the receipt key in SpecialPage. If using order Routes, as in the foundation demo, you set it with the receipt key to Route.

4.5. The Order Counter

An order counter can be enabled if the OrderCounter directive is set to a file name. An incrementing count of all orders will be kept and assigned as orders are placed. By default, the number starts at 0, but you can edit the file and change the default starting number at any time.

This capability is made possible by the File::CounterFile module, available (as a part of the fine libwww modules) at the same place you got Interchange. It is included with the distribution.

4.6. Customer Input Fields

On the order (or shopping basket) page, by default order.html, you will have a number of input fields allowing the customer to enter information such as their name and address. You can add more fields simply by putting more input elements on the order.html page, and the information will automatically be included in the order report. Input elements should be written in this way:

    <input type="text" name="town" value="[value town]" size=30>

Choose a name for this input field such as "email" for an email address. Set the name attribute to the name you have chosen.

The value attribute specifies the default value to give the field when the page is displayed. Because the customer may enter information on the order page, return to browsing, and come back to the order page, you want the default value to be what was entered the first time. This is done with the [value var] element, which returns the last value of an input field. Thus,

    value="[value name]"

will evaluate to the name entered on the previous order screen, such as:

       value="Jane Smith"

which will be displayed by the browser.

The size attributes specifies how many characters wide the input field should be on the browser. You do not need to set this to fit the longest possible value since the browser will scroll the field, but you should set it large enough to be comfortable for the customer.

Copyright 2002-2004 Interchange Development Group. Copyright 2001-2002 Red Hat, Inc. Freely redistributable under terms of the GNU General Public License.