re-work coupons to allow multiple coupon types
[bse.git] / site / cgi-bin / modules / BSE / Coupon / Base.pm
1 package BSE::Coupon::Base;
2 use strict;
3
4 our $VERSION = "1.001";
5
6 sub new {
7   my ($class, $config) = @_;
8
9   return bless { config => $config }, $class;
10 }
11
12 sub config_rules {
13   return {};
14 }
15
16 sub used_in {
17   # do nothing by default
18 }
19
20 sub cart_wide {
21   1;
22 }
23
24 sub product_valid {
25   0;
26 }
27
28 sub product_discount {
29   0;
30 }
31
32 sub product_discount_units {
33   0;
34 }
35
36 sub test_all_tiers_match {
37   my ($self, $coupon, $cart) = @_;
38
39   my %tiers = map { $_ => 1 } $coupon->tiers;
40
41   my $bad_tier_count = 0;
42   for my $item ($cart->items) {
43     if ($item->tier_id) {
44       if (!$tiers{$item->tier_id}) {
45         return 0;
46       }
47     }
48     else {
49       if (!$coupon->untiered) {
50         return 0;
51       }
52     }
53   }
54
55   return 1;
56 }
57
58 sub test_tier_matches {
59   my ($self, $coupon, $cart, $index) = @_;
60
61   my @items = $cart->items;
62   $index >= 0 && $index < @items
63     or return 0;
64
65   my $item = $items[$index];
66
67   if ($item->tier_id) {
68     my %tiers = map { $_ => 1 } $coupon->tiers;
69
70     if (!$tiers{$item->tier_id}) {
71       return 0;
72     }
73   }
74   else {
75     if (!$coupon->untiered) {
76       return 0;
77     }
78   }
79
80   return 1;
81 }
82
83 1;
84
85 __END__
86
87 =head1 NAME
88
89 BSE::Coupon::Base - base class for coupon behaviour classes.
90
91 =head1 SYNOPSIS
92
93   package BSE::Coupon::YourCoupon;
94   use parent 'BSE::Coupon::Base';
95
96   sub new {
97     my ($class, $config) = @_;
98     ...
99   }
100
101   # ... and more
102
103 =head1 DESCRIPTION
104
105 This class provides base behaviour and documentation on the
106 requirements of BSE coupon behaviour classes.
107
108 A coupon behaviour class must use BSE::Coupon::Base as a base class,
109 to provide default behaviour that might be added later to this
110 specification.
111
112 A coupon behaviour class may override the default constructor:
113
114 =over
115
116 =item *
117
118 new($config) - create a new coupon behaviour object.  This is passed a
119 single parameter of the config hash for that behaviour, as specified
120 by config_fields().
121
122 =back
123
124 The class must implement the following class methods:
125
126 =over
127
128 =item *
129
130 config_fields() - returns a hash reference of customization
131 fields for this coupon class.  For example it might return a field
132 definition for the dollar amount to be discounted.
133
134 If a field called C<discount_percent> is included it will be stored in
135 the C<discount_percent> field of the coupon entry.
136
137   my $fields = $class->config_fields();
138
139 =item *
140
141 config_rules() - any extra rule definitions required for the
142 validation rules in config_fields().
143
144   my $rules = $class->config_rules();
145
146 A default implementation is provided that returns an empty hash.
147
148 =item *
149
150 config_valid() - validate whether a supplied configuration is valid.
151
152   my $valid = $class->config_valid($config, \%errors);
153
154 This occurs in addition to any validation rules in the fields returned
155 by config_fields().
156
157 =item *
158
159 class_description() - a brief description of this coupon behaviour,
160 for use in drop-down lists when creating a coupon.
161
162 =back
163
164 The class must implement the following object methods:
165
166 =over
167
168 =item *
169
170 is_active() - return true if the coupon is usable for the cart.  If
171 the coupon is not usable, returns $msg as the reson why:
172
173   my ($active, $msg) = $cb->is_active($coupon, $cart);
174
175 This is in addition to the date and tier checks already done for the coupon.
176
177 =item *
178
179 discount() - return the discount in cents provided by the coupon on
180 the supplied cart.
181
182   my ($cents) = $cb->discount($coupon, $cart);
183
184 Must only be called if is_active() returns true for the cart.
185
186 =item *
187
188 product_valid($coupon, $cart, $index) - returns true if the given line
189 item in the cart is valid for the coupon.
190
191 Only meaningful for cart-wide coupons.
192
193 =item *
194
195 product_discount($coupon, $cart, $index) - returns the per-unit
196 discount in cents provided by the coupon on the specified entry in the
197 given cart.
198
199   my ($cents) = $cb->product_discount($coupon, $cart, $index);
200
201 Must only be called if is_active() returns true for the cart.
202
203 Returns 0 if the discount is against the entire cart.
204
205 A default implementation returns 0.
206
207 =item *
208
209 product_discount_units($coupon, $cart, $index) - returns the number of
210 units the product specific discount applies to on specified entry in
211 the given cart.
212
213   my ($cents) = $cb->product_discount_units($coupon, $cart, $index);
214
215 Must only be called if is_active() returns true for the cart.
216
217 Returns 0 if the discount is against the entire cart.
218
219 A default implementation returns 0.
220
221 =item *
222
223 cart_wide($coupon, $cart) - return true if the coupon behaviour
224 provides a cart-wide discount.  Generally this is class-wide, but must
225 be called as an instance method in-case that's untrue for some
226 particular behaviour class.
227
228 The default implementation returns false.
229
230 =item *
231
232 used_in() - called when a new order if finalized with the given coupon
233 code.
234
235   $cb->used_in($coupon_code, $order);
236
237 A default implementation is provided that does nothing.
238
239 =item *
240
241 describe() - return a text description of the coupon based on its
242 configuration data.
243
244   my ($text) = $cb->describe();
245
246 =back
247