]> git.imager.perl.org - bse.git/blame - site/cgi-bin/modules/BSE/TB/Order.pm
allow purchase of products with missing options
[bse.git] / site / cgi-bin / modules / BSE / TB / Order.pm
CommitLineData
0ec4ac8a
TC
1package BSE::TB::Order;
2use strict;
3# represents an order from the database
4use Squirrel::Row;
5use vars qw/@ISA/;
6@ISA = qw/Squirrel::Row/;
5d88571c 7use Carp 'confess';
b62cae00 8use BSE::Shop::PaymentTypes;
0ec4ac8a 9
5014aef9 10our $VERSION = "1.030";
cb7fd78d 11
0ec4ac8a
TC
12sub columns {
13 return qw/id
14 delivFirstName delivLastName delivStreet delivSuburb delivState
15 delivPostCode delivCountry
16 billFirstName billLastName billStreet billSuburb billState
17 billPostCode billCountry
18 telephone facsimile emailAddress
19 total wholesaleTotal gst orderDate
20 ccNumberHash ccName ccExpiryHash ccType
21 filled whenFilled whoFilled paidFor paymentReceipt
22 randomId cancelled userId paymentType
23 customInt1 customInt2 customInt3 customInt4 customInt5
24 customStr1 customStr2 customStr3 customStr4 customStr5
25 instructions billTelephone billFacsimile billEmail
e3d242f7 26 siteuser_id affiliate_code shipping_cost
41e7c841
TC
27 delivMobile billMobile
28 ccOnline ccSuccess ccReceipt ccStatus ccStatusText
37dd20ad 29 ccStatus2 ccTranId complete delivOrganization billOrganization
d9803c26 30 delivStreet2 billStreet2 purchase_order shipping_method
13a986ee 31 shipping_name shipping_trace
b62cae00 32 paypal_token paypal_tran_id freight_tracking stage ccPAN
b55d4af1
TC
33 paid_manually coupon_id coupon_code_discount_pc delivery_in
34 product_cost_discount coupon_cart_wide coupon_description/;
f0722dd2
TC
35}
36
37sub table {
38 return "orders";
0ec4ac8a
TC
39}
40
14604ada
TC
41sub defaults {
42 require BSE::Util::SQL;
43 require Digest::MD5;
44 return
45 (
f0722dd2
TC
46 billFirstName => "",
47 billLastName => "",
48 billStreet => "",
49 billSuburb => "",
50 billState => "",
51 billPostCode => "",
52 billCountry => "",
14604ada
TC
53 total => 0,
54 wholesaleTotal => 0,
55 gst => 0,
56 orderDate => BSE::Util::SQL::now_datetime(),
57 filled => 0,
58 whenFilled => undef,
59 whoFilled => '',
60 paidFor => 0,
61 paymentReceipt => '',
62 randomId => Digest::MD5::md5_hex(time().rand().{}.$$),
63 ccNumberHash => '',
64 ccName => '',
65 ccExpiryHash => '',
66 ccType => '',
67 randomId => '',
68 cancelled => 0,
69 userId => '',
70 paymentType => 0,
71 customInt1 => undef,
72 customInt2 => undef,
73 customInt3 => undef,
74 customInt4 => undef,
75 customInt5 => undef,
76 customStr1 => undef,
77 customStr2 => undef,
78 customStr3 => undef,
79 customStr4 => undef,
80 customStr5 => undef,
81 instructions => '',
82 siteuser_id => undef,
83 affiliate_code => '',
84 shipping_cost => 0,
85 ccOnline => 0,
86 ccSuccess => 0,
87 ccReceipt => '',
88 ccStatus => 0,
89 ccStatusText => '',
90 ccStatus2 => '',
91 ccTranId => '',
92 complete => 0,
93 purchase_order => '',
94 shipping_method => '',
95 shipping_name => '',
96 shipping_trace => undef,
f0722dd2
TC
97 paypal_token => "",
98 paypal_tran_id => "",
080fc207 99 freight_tracking => "",
f0722dd2 100 stage => "incomplete",
6abd8ce8 101 ccPAN => "",
b62cae00 102 paid_manually => 0,
557b937f
TC
103 coupon_id => undef,
104 coupon_code_discount_pc => 0,
c6369510 105 delivery_in => undef,
557b937f
TC
106 product_cost_discount => 0,
107 coupon_cart_wide => 1,
108 coupon_description => '',
14604ada
TC
109 );
110}
111
112sub address_columns {
113 return qw/
114 delivFirstName delivLastName delivStreet delivSuburb delivState
115 delivPostCode delivCountry
116 billFirstName billLastName billStreet billSuburb billState
117 billPostCode billCountry
118 telephone facsimile emailAddress
119 instructions billTelephone billFacsimile billEmail
120 delivMobile billMobile
121 delivOrganization billOrganization
122 delivStreet2 billStreet2/;
123}
124
125sub user_columns {
126 return qw/userId siteuser_id/;
127}
128
129sub payment_columns {
130 return qw/ccNumberHash ccName ccExpiryHash ccType
131 paidFor paymentReceipt paymentType
132 ccOnline ccSuccess ccReceipt ccStatus ccStatusText
b62cae00 133 ccStatus2 ccTranId ccPAN paid_manually/;
14604ada
TC
134}
135
c4f18087
TC
136=item billing_to_delivery_map
137
138Return a hashref where the key is a billing field and the value is the
139corresponding delivery field.
140
141=cut
142
143{
144 my %billing_to_delivery =
145 (
146 billEmail => "emailAddress",
147 billFirstName => "delivFirstName",
148 billLastName => "delivLastName",
149 billStreet => "delivStreet",
150 billStreet2 => "delivStreet2",
151 billSuburb => "delivSuburb",
152 billState => "delivState",
153 billPostCode => "delivPostCode",
154 billCountry => "delivCountry",
155 billTelephone => "telephone",
a964e89d 156 billMobile => "delivMobile",
c4f18087
TC
157 billFacsimile => "facsimile",
158 billOrganization => "delivOrganization",
159 );
160
161 sub billing_to_delivery_map {
162 return \%billing_to_delivery;
163 }
164}
165
0ec4ac8a
TC
166=item siteuser
167
168returns the SiteUser object of the user who made this order.
169
170=cut
171
172sub siteuser {
173 my ($self) = @_;
174
f0722dd2 175 if ($self->siteuser_id) {
b7cadc84
AO
176 require BSE::TB::SiteUsers;
177 my $user = BSE::TB::SiteUsers->getByPkey($self->siteuser_id);
f0722dd2
TC
178 $user and return $user;
179 }
180
0ec4ac8a
TC
181 $self->{userId} or return;
182
b7cadc84 183 require BSE::TB::SiteUsers;
0ec4ac8a 184
b7cadc84 185 return ( BSE::TB::SiteUsers->getBy(userId=>$self->{userId}) )[0];
0ec4ac8a
TC
186}
187
188sub items {
189 my ($self) = @_;
190
191 require BSE::TB::OrderItems;
192 return BSE::TB::OrderItems->getBy(orderId => $self->{id});
193}
194
ab2cd916
TC
195sub files {
196 my ($self) = @_;
197
7c6f563b
TC
198 require BSE::TB::ArticleFiles;
199 return BSE::TB::ArticleFiles->getSpecial(orderFiles=>$self->{id});
ab2cd916
TC
200}
201
eb9d306d
TC
202sub paid_files {
203 my ($self) = @_;
204
205 $self->paidFor
206 or return;
207
208 require BSE::TB::ArticleFiles;
209 return BSE::TB::ArticleFiles->getSpecial(orderPaidFor => $self->id);
210}
211
ab2cd916
TC
212sub products {
213 my ($self) = @_;
214
10dd37f9
AO
215 require BSE::TB::Products;
216 BSE::TB::Products->getSpecial(orderProducts=>$self->{id});
ab2cd916
TC
217}
218
41e7c841
TC
219sub valid_fields {
220 my ($class, $cfg) = @_;
221
222 my %fields =
223 (
b27af108 224 delivFirstName => { description=>'Delivery First Name',
37dd20ad 225 rules=>'dh_one_line' },
b27af108 226 delivLastName => { description => 'Delivery Last Name',
37dd20ad 227 rules=>'dh_one_line' },
b27af108 228 delivOrganization => { description => 'Delivery Organization',
37dd20ad 229 rules=>'dh_one_line' },
b27af108 230 delivStreet => { description => 'Delivery Street',
37dd20ad 231 rules=>'dh_one_line' },
b27af108 232 delivStreet2 => { description => 'Delivery Street 2',
37dd20ad 233 rules=>'dh_one_line' },
b27af108 234 delivState => { description => 'Delivery State',
37dd20ad 235 rules=>'dh_one_line' },
b27af108 236 delivSuburb => { description => 'Delivery Suburb',
37dd20ad 237 rules=>'dh_one_line' },
b27af108 238 delivPostCode => { description => 'Delivery Post Code',
9074efa2 239 rules=>'dh_one_line;dh_int_postcode' },
b27af108 240 delivCountry => { description => 'Delivery Country',
37dd20ad 241 rules=>'dh_one_line' },
b27af108 242 billFirstName => { description => 'Billing First Name',
37dd20ad 243 rules=>'dh_one_line' },
b27af108 244 billLastName => { description => 'Billing Last Name',
37dd20ad 245 rules=>'dh_one_line' },
b27af108 246 billOrganization => { description => 'Billing Organization',
37dd20ad 247 rules=>'dh_one_line' },
b27af108 248 billStreet => { description => 'Billing Street',
37dd20ad 249 rules=>'dh_one_line' },
b27af108 250 billStreet2 => { description => 'Billing Street 2',
37dd20ad 251 rules=>'dh_one_line' },
b27af108 252 billSuburb => { description => 'Billing Suburb',
37dd20ad 253 rules=>'dh_one_line' },
b27af108 254 billState => { description => 'Billing State',
37dd20ad 255 rules=>'dh_one_line' },
b27af108 256 billPostCode => { description => 'Billing Post Code',
9074efa2 257 rules=>'dh_one_line;dh_int_postcode' },
b27af108 258 billCountry => { description => 'Billing First Name',
37dd20ad 259 rules=>'dh_one_line' },
41e7c841
TC
260 telephone => { description => 'Telephone Number',
261 rules => "phone" },
262 facsimile => { description => 'Facsimile Number',
263 rules => 'phone' },
264 emailAddress => { description => 'Email Address',
c4f18087 265 rules=>'email' },
41e7c841 266 instructions => { description => 'Instructions' },
b27af108 267 billTelephone => { description => 'Billing Telephone Number',
41e7c841
TC
268 rules=>'phone' },
269 billFacsimile => { description => 'Billing Facsimile Number',
270 rules=>'phone' },
271 billEmail => { description => 'Billing Email Address',
c4f18087 272 rules => 'email;required' },
41e7c841
TC
273 delivMobile => { description => 'Delivery Mobile Number',
274 rules => 'phone' },
275 billMobile => { description => 'Billing Mobile Number',
276 rules=>'phone' },
277 instructions => { description => 'Instructions' },
74b21f6d 278 purchase_order => { description => 'Purchase Order No' },
d8674b8b
AMS
279 shipping_cost => { description => 'Shipping charges' },
280 shipping_method => { description => 'Shipping method' },
41e7c841
TC
281 );
282
283 for my $field (keys %fields) {
284 my $display = $cfg->entry('shop', "display_$field");
285 $display and $fields{$field}{description} = $display;
286 }
287
288 return %fields;
289}
290
291sub valid_rules {
292 my ($class, $cfg) = @_;
293
294 return;
295}
296
297sub valid_payment_fields {
298 my ($class, $cfg) = @_;
299
300 my %fields =
301 (
b27af108
TC
302 cardNumber =>
303 {
41e7c841
TC
304 description => "Credit Card Number",
305 rules=>"creditcardnumber",
306 },
b27af108 307 cardExpiry =>
41e7c841
TC
308 {
309 description => "Credit Card Expiry Date",
310 rules => 'creditcardexpirysingle',
311 },
6abd8ce8 312 ccName => { description => "Credit Card Holder" },
1546e1f0 313 ccType => { description => "Credit Card Type" },
b27af108
TC
314 cardVerify =>
315 {
41e7c841
TC
316 description => 'Card Verification Value',
317 rules => 'creditcardcvv',
318 },
319 );
320
321 for my $field (keys %fields) {
322 my $display = $cfg->entry('shop', "display_$field");
323 $display and $fields{$field}{description} = $display;
324 }
325
326 return %fields;
327}
328
329sub valid_payment_rules {
330 return;
331}
332
5d88571c
TC
333sub clear_items {
334 my ($self) = @_;
335
336 confess "Attempt to clear items on completed order $self->{id}"
337 if $self->{complete};
b27af108 338
5d88571c
TC
339 BSE::DB->run(deleteOrdersItems => $self->{id});
340}
341
14604ada
TC
342sub add_item {
343 my ($self, %opts) = @_;
344
345 my $prod = delete $opts{product}
346 or confess "Missing product option";
347 my $units = delete $opts{units} || 1;
348
349 my $options = '';
350 my @dboptions;
351 if ($opts{options}) {
352 if (ref $opts{options}) {
353 @dboptions = @{delete $opts{options}};
354 }
355 else {
356 $options = delete $opts{options};
357 }
358 }
b27af108 359
14604ada
TC
360 require BSE::TB::OrderItems;
361 my %item =
362 (
363 productId => $prod->id,
364 orderId => $self->id,
365 units => $units,
366 price => $prod->retailPrice,
367 options => $options,
368 max_lapsed => 0,
369 session_id => 0,
370 ( map { $_ => $prod->{$_} }
371 qw/wholesalePrice gst customInt1 customInt2 customInt3 customStr1 customStr2 customStr3 title description subscription_id subscription_period product_code/
372 ),
373 );
374
375 $self->set_total($self->total + $prod->retailPrice * $units);
376
377 return BSE::TB::OrderItems->make(%item);
378}
379
13a986ee
TC
380sub deliv_country_code {
381 my ($self) = @_;
382
383 my $use_codes = BSE::Cfg->single->entry("shop", "country_code", 0);
384 if ($use_codes) {
385 return $self->delivCountry;
386 }
387 else {
388 require BSE::Countries;
389 return BSE::Countries::bse_country_code($self->delivCountry);
390 }
391}
392
f0722dd2
TC
393=item stage
394
395Return the order stage.
396
397If the stage is empty, guess from the order flags.
398
399=cut
400
401sub stage {
402 my ($self) = @_;
403
404 if ($self->{stage} ne "") {
405 return $self->{stage};
406 }
407
408 if (!$self->complete) {
409 return "incomplete";
410 }
411 elsif ($self->filled) {
412 return "shipped";
413 }
414 else {
415 return "unprocessed";
416 }
417}
418
419sub stage_description {
420 my ($self, $lang) = @_;
421
422 return BSE::TB::Orders->stage_label($self->stage, $lang);
423}
424
f55be9df
TC
425sub stage_description_id {
426 my ($self) = @_;
427
428 return BSE::TB::Orders->stage_label_id($self->stage);
429}
430
c4f18087 431=item delivery_mail_recipient
f0722dd2 432
c4f18087
TC
433Return a value suitable for BSE::ComposeMail's to parameter for the
434shipping email address.
f0722dd2
TC
435
436=cut
437
c4f18087 438sub delivery_mail_recipient {
f0722dd2
TC
439 my ($self) = @_;
440
441 my $user = $self->siteuser;
c4f18087 442 my $email = $self->emailAddress || $self->billEmail;
f0722dd2 443
c4f18087 444 if ($user && $user->email eq $email) {
f0722dd2
TC
445 return $user;
446 }
447
c4f18087 448 return $email;
f0722dd2
TC
449}
450
768dccf0 451=item _tags
8d8895b4 452
768dccf0 453Internal method with the common code between tags() and mail_tags().
8d8895b4
TC
454
455=cut
456
768dccf0
TC
457sub _tags {
458 my ($self, $escape) = @_;
8d8895b4
TC
459
460 require BSE::Util::Tags;
8d8895b4 461 require BSE::TB::OrderItems;
768dccf0
TC
462 require BSE::Util::Iterate;
463 my $it;
464 my $art;
465 my $esc;
466 my $obj;
467 if ($escape) {
468 require BSE::Util::HTML;
469 $it = BSE::Util::Iterate::Objects->new;
470 $art = \&BSE::Util::Tags::tag_article;
471 $obj = \&BSE::Util::Tags::tag_object;
472 $esc = \&BSE::Util::HTML::escape_html;
473 }
474 else {
475 $it = BSE::Util::Iterate::Objects::Text->new;
476 $art = \&BSE::Util::Tags::tag_article_plain;
477 $obj = \&BSE::Util::Tags::tag_object_plain;
478 $esc = sub { return $_[0] };
479 }
480
481 my $cfg = BSE::Cfg->single;
482 my $must_be_paid = $cfg->entryBool('downloads', 'must_be_paid', 0);
483 my $must_be_filled = $cfg->entryBool('downloads', 'must_be_filled', 0);
484
8d8895b4
TC
485 my %item_cols = map { $_ => 1 } BSE::TB::OrderItem->columns;
486 my %products;
487 my $current_item;
768dccf0 488 my $current_file;
8d8895b4
TC
489 return
490 (
768dccf0 491 order => [ $obj, $self ],
8d8895b4
TC
492 $it->make
493 (
494 single => "item",
495 plural => "items",
496 code => [ items => $self ],
497 store => \$current_item,
498 ),
499 extended => sub {
500 my ($args) = @_;
501
502 $current_item
503 or return '* only usable in items iterator *';
504
505 $item_cols{$args}
506 or return "* unknown item column $args *";
507
508 return $current_item->$args() * $current_item->units;
509 },
510 $it->make
511 (
512 single => "option",
513 plural => "options",
514 code => sub {
515 $current_item
516 or return;
517 return $current_item->option_hashes
518 },
519 nocache => 1,
520 ),
521 options => sub {
522 $current_item
523 or return '* only in the items iterator *';
768dccf0 524 return $esc->($current_item->nice_options);
8d8895b4
TC
525 },
526 product => sub {
527 $current_item
528 or return '* only usable in items *';
529
10dd37f9 530 require BSE::TB::Products;
8d8895b4 531 my $id = $current_item->productId;
10dd37f9 532 $products{$id} ||= BSE::TB::Products->getByPkey($id);
8d8895b4
TC
533
534 my $product = $products{$id}
535 or return '';
536
768dccf0
TC
537 return $art->($product, $cfg, $_[0]);
538 },
7c6f563b 539 $it->make
768dccf0
TC
540 (
541 single => 'orderfile',
542 plural => 'orderfiles',
543 code => [ files => $self ],
544 store => \$current_file,
545 ),
7c6f563b 546 $it->make
768dccf0
TC
547 (
548 single => "prodfile",
549 plural => "prodfiles",
550 code => sub {
551 $current_item
552 or return '* only usable in items *';
553
10dd37f9 554 require BSE::TB::Products;
768dccf0 555 my $id = $current_item->productId;
10dd37f9 556 $products{$id} ||= BSE::TB::Products->getByPkey($id);
768dccf0
TC
557
558 my $product = $products{$id}
559 or return '';
560
561 return $product->files;
562 },
563 store => \$current_file,
564 ),
565 ifFileAvail => sub {
566 $current_file or return 0;
567 $current_file->{forSale} or return 1;
568
569 return 0 if $must_be_paid && !$self->{paidFor};
570 return 0 if $must_be_filled && !$self->{filled};
571
572 return 1;
8d8895b4
TC
573 },
574 );
575}
576
25a17f89
TC
577sub cfg_must_be_paid {
578 BSE::Cfg->single->entryBool("download", "must_be_paid", 0);
579}
580
581sub cfg_must_be_filled {
582 BSE::Cfg->single->entryBool("download", "must_be_filled", 0);
583}
584
585=item file_available
586
587Given an order file, return true if available for download.
588
589This will return nonsensical results for files not associated with the
590order.
591
592=cut
593
594sub file_available {
595 my ($self, $file) = @_;
596
597 $file->forSale or return 1;
598
599 return 0 if $self->cfg_must_be_paid && !$self->paidFor;
600 return 0 if $self->cfg_must_be_filled && !$self->filled;
601
602 return 1;
603}
604
768dccf0
TC
605=item mail_tags
606
607=cut
608
609sub mail_tags {
610 my ($self) = @_;
611
612 return $self->_tags(0);
613}
614
615=item tags
616
617Return template tags suitable for an order (non-mail)
618
619=cut
620
621sub tags {
622 my ($self) = @_;
623
624 return $self->_tags(1);
625}
626
f0722dd2
TC
627sub send_shipped_email {
628 my ($self) = @_;
629
c4f18087 630 my $to = $self->delivery_mail_recipient;
f0722dd2
TC
631 require BSE::ComposeMail;
632 my $mailer = BSE::ComposeMail->new(cfg => BSE::Cfg->single);
633 require BSE::Util::Tags;
f0722dd2
TC
634 my %acts =
635 (
636 BSE::Util::Tags->mail_tags(),
8d8895b4 637 $self->mail_tags,
f0722dd2 638 );
c4f18087 639 my %opts =
f0722dd2
TC
640 (
641 to => $to,
642 subject => "Your order has shipped",
643 template => "email/ordershipped",
644 acts => \%acts,
68d44fe0 645 log_msg => "Notify customer that Order No. " . $self->id . " has shipped",
f0722dd2
TC
646 log_object => $self,
647 log_component => "shopadmin:orders:saveorder",
44af429d 648 vars => { order => $self },
f0722dd2 649 );
c4f18087
TC
650 if ($self->emailAddress && $self->billEmail
651 && lc $self->emailAddress ne $self->billEmail) {
652 $opts{cc} = $self->billEmail;
653 }
654
655 $mailer->send(%opts);
f0722dd2
TC
656}
657
658sub new_stage {
659 my ($self, $who, $stage, $stage_note) = @_;
660
661 unless ($stage ne $self->stage
662 || defined $stage_note && $stage_note =~ /\S/) {
663 return;
664 }
665
666 my $old_stage = $self->stage;
68d44fe0 667 my $msg = "Set Order No. ". $self->id . " stage to '$stage'";
f0722dd2
TC
668 if (defined $stage_note && $stage_note =~ /\S/) {
669 $msg .= ": $stage_note";
670 }
671 require BSE::TB::AuditLog;
672 BSE::TB::AuditLog->log
673 (
674 component => "shopadmin:orders:saveorder",
675 object => $self,
676 msg => $msg,
d9c45dcc 677 level => "notice",
f0722dd2
TC
678 actor => $who || "U"
679 );
680
681 if ($stage ne $old_stage) {
682 $self->set_stage($stage);
683 if ($stage eq "shipped") {
4b9daeca
TC
684 if (!$self->filled) {
685 require BSE::Util::SQL;
686
687 $self->set_whoFilled($who ? $who->logon : "-unknown-");
688 $self->set_whenFilled(BSE::Util::SQL::now_datetime());
689 }
f0722dd2
TC
690 $self->send_shipped_email();
691 $self->set_filled(1);
692 }
693 else {
694 $self->set_filled(0);
695 }
696 }
697}
698
6abd8ce8
TC
699sub set_ccPANTruncate {
700 my ($self, $pan) = @_;
701
702 if (length $pan > 4) {
703 $pan = substr($pan, -4);
704 }
705
706 $self->set_ccPAN($pan);
707}
708
b62cae00
TC
709=item is_manually_paid
710
711Returns true if the order is marked as manually paid, either through
712the older PAYMENT_MANUAL paymentType value or via the newer flag.
713
714=cut
715
716sub is_manually_paid {
717 my ($self) = @_;
718
719 return $self->paidFor &&
720 ($self->paid_manually || $self->paymentType == PAYMENT_MANUAL);
721}
722
91a02f51
TC
723=item coupon_valid
724
725For compatibility with cart objects, returns true if the currently
726stored coupon is valid.
727
728Since only an active coupon is stored, if we have a coupon code, then
729it's valid.
730
731=cut
732
733sub coupon_valid {
734 my ($self) = @_;
735
2ced88e0 736 return defined($self->coupon_id);
91a02f51
TC
737}
738
739=item coupon_active
740
741For compatibility with cart objects, returns true if the currently
742stored coupon is active.
743
744Since only an active coupon is stored, if we have a coupon code, then
745it's valid.
746
747=cut
748
749*coupon_active = \&coupon_valid;
750
751=item total_cost
752
753Return the total cost of products without the coupon discount applied.
754
755=cut
756
757sub total_cost {
758 my ($self) = @_;
759
760 my $total = 0;
761 for my $item ($self->items) {
762 $total += $item->extended("price");
763 }
764
765 return $total;
766}
767
768=item discounted_product_cost
769
770Return the total cost of products less the discount from the coupon
771code.
772
773=cut
774
775sub discounted_product_cost {
776 my ($self) = @_;
777
778 my $cost = $self->total_cost;
779
b55d4af1
TC
780 if ($self->product_cost_discount) {
781 return $cost - $self->product_cost_discount;
782 }
91a02f51 783
b55d4af1 784 $cost -= int($cost * $self->coupon_code_discount_pc / 100);
91a02f51 785
b55d4af1 786 return $cost;
91a02f51
TC
787}
788
2ced88e0
TC
789=item coupon
790
791Return the coupon used for this order, if any.
792
793=cut
794
795sub coupon {
796 my ($self) = @_;
797
798 $self->coupon_id
799 or return;
800
801 require BSE::TB::Coupons;
802 return BSE::TB::Coupons->getByPkey($self->coupon_id);
803}
804
9a3d402d
TC
805=item coupon_code
806
807Emulate the cart's coupon-code method.
808
809=cut
810
811sub coupon_code {
812 my ($self) = @_;
813
814 my $coupon = $self->coupon
815 or return;
816
817 return $coupon->code;
818}
819
0ec4ac8a 8201;