Shopping cart with arbitrary promotions model

Not exactly a rails question, but my cart is in rails :-p

I’m looking for a model which allows for promotions to make arbitrary
changes to a users shopping cart / session — pretty much anything my
account management wizards can dream up that they think might get more
sales. For example, some standards would be: discounting the entire
cart, discounting single items or a type of items, adding free items,
giving free/discounted shipping, flagging a user so their next purchase
applies for a different promotion, etc.

Please ignore my further prattling unless you’re interested.

I have a functioning cart that does this already, but it has problems
which make introducing new types of promotions difficult. Namely, too
much responsibility lies with the cart itself. My cart requires
knowledge of the way the different types of promotions work, e.g. when
calculating shipping costs it looks to promotions which affect shipping.
When calculating discounts it looks to promotions that do discounts.
When its state changes it looks to promotions that alter its contents,
e.g. adding/removing bonus items if the promotion qualifies/fails to
qualify. This works, but it means a few things:

1.) The cart has responsibility for applying/unapplying promotions.
2.) Adding new promotion types means rewriting cart code to accommodate
them, sometimes very convoluted code.

What I’d like is a model that somehow abstracts promotions away from the
cart, but I can’t think of a good way to do it. Some kind of proxy
might work perhaps, where a Promotion encapsulates the cart and proxies
its methods, introducing its own code which alters the order. This gets
messy though with multiple promotions. Currently my cart doesn’t
require more than one promotion at a time, but i don’t want to write
this code then have them come back next month with “Oh yeah, we want to
be able to attach both a promotion and a coupon to the order.”

Anyone seen any interesting cart implementations that deal with this
sort of thing?