coupon code processing from cart to order final
authorTony Cook <tony@develop-help.com>
Thu, 13 Jun 2013 01:20:35 +0000 (11:20 +1000)
committerTony Cook <tony@develop-help.com>
Sun, 21 Jul 2013 23:34:15 +0000 (09:34 +1000)
This includes a lot of internal shop changes

site/cgi-bin/modules/BSE/Cart.pm
site/cgi-bin/modules/BSE/UI/Shop.pm
site/templates/cart_base.tmpl
site/templates/checkoutfinal_base.tmpl
site/templates/checkoutnew_base.tmpl

index b1a7044..c31bfbc 100644 (file)
@@ -2,7 +2,7 @@ package BSE::Cart;
 use strict;
 use Scalar::Util;
 
-our $VERSION = "1.004";
+our $VERSION = "1.005";
 
 =head1 NAME
 
@@ -36,6 +36,8 @@ Create a new cart object based on the session.
 sub new {
   my ($class, $req, $stage) = @_;
 
+  $stage ||= "";
+
   my $self = bless
     {
      products => {},
@@ -53,6 +55,9 @@ sub new {
   if ($stage eq 'cart' || $stage eq 'checkout') {
     $self->_enter_cart;
   }
+  elsif ($stage eq 'checkupdate') {
+    $self->_checkout_update;
+  }
 
   $self->{coupon_code} = $self->{req}->session->{cart_coupon_code};
   defined $self->{coupon_code} or $self->{coupon_code} = "";
@@ -72,10 +77,39 @@ sub _enter_cart {
   $self->{custom_state} = \%custom_state;
 
   my $cust_class = BSE::CfgInfo::custom_class($self->{req}->cfg);
-  $cust_class->enter_cart($self->items, $self->products,
+  $cust_class->enter_cart(scalar $self->items, scalar $self->products,
                          \%custom_state, $req->cfg);
 }
 
+sub _checkout_update {
+  my ($self) = @_;
+
+  my $req = $self->{req};
+  require BSE::CfgInfo;
+
+  $req->session->{custom} ||= {};
+  my %custom_state = %{$req->session->{custom}};
+
+  $self->{custom_state} = \%custom_state;
+
+  my $cust_class = BSE::CfgInfo::custom_class($self->{req}->cfg);
+  $cust_class->checkout_update
+    ($req->cgi, scalar $self->items, scalar $self->products,
+     \%custom_state, $req->cfg);
+}
+
+=item is_empty()
+
+Return true if the cart has no items in it.
+
+=cut
+
+sub is_empty {
+  my ($self) = @_;
+
+  return @{$self->{items}} == 0;
+}
+
 =item items()
 
 Return an array reference of cart items.
@@ -120,11 +154,51 @@ sub total_cost {
   return $total_cost;
 }
 
+=item gst
+
+Return the total GST paid for the items in the cart.
+
+This currently depends on the gst values of the products.
+
+This ignores the coupon code discount.
+
+=cut
+
+sub gst {
+  my ($self) = @_;
+
+  my $total_gst = 0;
+  for my $item (@{$self->items}) {
+    $total_gst += $item->extended_gst;
+  }
+
+  return $total_gst;
+}
+
+=item wholesaleTotal
+
+Return the wholesale cost for the items in the cart.
+
+This depends on the wholesale values of the products.
+
+=cut
+
+sub wholesaleTotal {
+  my ($self) = @_;
+
+  my $total_wholesale = 0;
+  for my $item (@{$self->items}) {
+    $total_wholesale += $item->extended_wholesale;
+  }
+
+  return $total_wholesale;
+}
+
 =item discounted_product_cost
 
 Cost of products with an product discount taken into account.
 
-Note: this rounds thr total B<down>.
+Note: this rounds the total B<down>.
 
 =cut
 
@@ -189,7 +263,7 @@ sub total_units {
 
   my $total_units = 0;
   for my $item (@{$self->items}) {
-    $total_units += $item->{units};
+    $total_units += $item->units;
   }
 
   return $total_units;
@@ -197,9 +271,7 @@ sub total_units {
 
 =item total
 
-Total of items in the cart and shipping costs.
-
-This doesn't handle custom costs yet.
+Total of items in the cart, any custom costs and shipping costs.
 
 =cut
 
@@ -617,6 +689,21 @@ sub cleanup {
   $self->{items} = \@newitems;
 }
 
+=item empty
+
+Empty the cart.
+
+=cut
+
+sub empty {
+  my ($self) = @_;
+
+  my $req = $self->{req};
+
+  # empty the cart ready for the next order
+  delete @{$req->session}{qw/order_info order_info_confirmed order_need_delivery cart order_work cart_coupon_code/};
+}
+
 =back
 
 =cut
index d968855..78ca98e 100644 (file)
@@ -18,7 +18,7 @@ use BSE::Countries qw(bse_country_code);
 use BSE::Util::Secure qw(make_secret);
 use BSE::Template;
 
-our $VERSION = "1.039";
+our $VERSION = "1.040";
 
 =head1 NAME
 
@@ -151,9 +151,9 @@ Flashes msg:bse/shop/cart/empty unless C<r> is supplied.
 sub req_emptycart {
   my ($self, $req) = @_;
 
-  my $old = $req->session->{cart};;
-  $req->session->{cart} = [];
-  delete $req->session->{cart_coupon_code};
+  my $cart = $req->cart;
+  my $item_count = @{$cart->items};
+  $cart->empty;
 
   my $refresh = $req->cgi->param('r');
   unless ($refresh) {
@@ -161,7 +161,7 @@ sub req_emptycart {
     $req->flash_notice("msg:bse/shop/cart/empty");
   }
 
-  return _add_refresh($refresh, $req, !$old);
+  return _add_refresh($refresh, $req, !$item_count);
 }
 
 sub req_add {
@@ -700,19 +700,22 @@ sub req_checkout {
 }
 
 sub req_checkupdate {
-  my ($class, $req) = @_;
+  my ($self, $req) = @_;
 
-  $req->session->{cart} ||= [];
-  my @cart = @{$req->session->{cart}};
-  my @cart_prods = map { Products->getByPkey($_->{productId}) } @cart;
-  $req->session->{custom} ||= {};
-  my %custom_state = %{$req->session->{custom}};
-  custom_class($req->cfg)
-      ->checkout_update($req->cgi, \@cart, \@cart_prods, \%custom_state, $req->cfg);
-  $req->session->{custom} = \%custom_state;
+  my $cart = $req->cart("checkupdate");
+
+  $self->update_quantities($req);
+
+  $req->session->{custom} = $cart->custom_state;
   $req->session->{order_info_confirmed} = 0;
-  
-  return $class->req_checkout($req, "", 1);
+
+  my %fields = $self->_order_fields($req);
+  my %values;
+  $self->_order_hash($req, \%values, \%fields);
+  $req->session->{order_info} = \%values;
+  $req->session->{order_need_delivery} = $req->cgi->param("need_delivery");
+
+  return $req->get_refresh($req->user_url(shop => "checkout"));
 }
 
 sub req_remove_item {
@@ -816,7 +819,7 @@ sub req_order {
   keys %errors
     and return $class->req_checkout($req, \%errors, 1);
 
-  $class->_fillout_order($req, \%values, \@items, \@products, \$msg, 'payment')
+  $class->_fillout_order($req, \%values, \$msg, 'payment')
     or return $class->req_checkout($req, $msg, 1);
 
   $req->session->{order_info} = \%values;
@@ -1078,10 +1081,13 @@ sub req_payment {
     }
   }
   else {
+    my $cart = $req->cart("payment");
+
     $order_values->{filled} = 0;
     $order_values->{paidFor} = 0;
     
-    my @items = $class->_build_items($req, \@products);
+    my @items = $class->_build_items($req);
+    @products = $cart->products;
     
     if ($session->{order_work}) {
       $order = BSE::TB::Orders->getByPkey($session->{order_work});
@@ -1104,7 +1110,7 @@ sub req_payment {
       my @allbutid = @columns;
       shift @allbutid;
       @{$order}{@allbutid} = @data;
-      
+
       $order->clear_items;
       delete $session->{order_work};
       eval {
@@ -1130,7 +1136,7 @@ sub req_payment {
       defined $item{session_id} or $item{session_id} = 0;
       $item{options} = ""; # not used for new orders
       my @data = @item{@item_cols};
-    shift @data;
+      shift @data;
       my $dbitem = BSE::TB::OrderItems->add(@data);
       push @dbitems, $dbitem;
       
@@ -1279,8 +1285,8 @@ sub _finish_order {
 
   $self->_send_order($req, $order);
 
-  # empty the cart ready for the next order
-  delete @{$req->session}{qw/order_info order_info_confirmed order_need_delivery cart order_work/};
+  my $cart = $req->cart;
+  $cart->empty;
 }
 
 =item orderdone
@@ -1410,6 +1416,7 @@ sub req_orderdone {
   }
 
   $req->set_variable(order => $order);
+  $req->set_variable(payment_types => \@pay_types);
 
   return $req->response('checkoutfinal', \%acts);
 }
@@ -1687,6 +1694,7 @@ sub tag_ifPayments {
 sub update_quantities {
   my ($class, $req) = @_;
 
+  # FIXME: should use the cart class to update quantities
   my $session = $req->session;
   my $cgi = $req->cgi;
   my $cfg = $req->cfg;
@@ -1717,9 +1725,10 @@ sub update_quantities {
 }
 
 sub _build_items {
-  my ($class, $req, $products) = @_;
+  my ($class, $req) = @_;
 
   my $session = $req->session;
+  my $cart = $req->cart;
   $session->{cart}
     or return;
   my @msgs;
@@ -1729,18 +1738,16 @@ sub _build_items {
   my @prodcols = Product->columns;
   my @newcart;
   my $today = now_sqldate();
-  for my $item (@cart) {
+  for my $item ($cart->items) {
     my %work = %$item;
-    my $product = Products->getByPkey($item->{productId});
+    my $product = $item->product;
     if ($product) {
-      (my $comp_release = $product->{release}) =~ s/ .*//;
-      (my $comp_expire = $product->{expire}) =~ s/ .*//;
-      $comp_release le $today
+      $product->is_released
        or do { push @msgs, "'$product->{title}' has not been released yet";
                next; };
-      $today le $comp_expire
-       or do { push @msgs, "'$product->{title}' has expired"; next; };
-      $product->{listed} 
+      $product->is_expired
+        and do { push @msgs, "'$product->{title}' has expired"; next; };
+      $product->listed
        or do { push @msgs, "'$product->{title}' not available"; next; };
 
       for my $col (@prodcols) {
@@ -1752,7 +1759,6 @@ sub _build_items {
       $work{extended_wholesale} = $work{units} * $work{wholesalePrice};
       
       push @newcart, \%work;
-      push @$products, $product;
     }
   }
 
@@ -1765,24 +1771,25 @@ sub _build_items {
 }
 
 sub _fillout_order {
-  my ($class, $req, $values, $items, $products, $rmsg, $how) = @_;
+  my ($class, $req, $values, $rmsg, $how) = @_;
 
   my $session = $req->session;
   my $cfg = $req->cfg;
   my $cgi = $req->cgi;
 
-  my $total = 0;
-  my $total_gst = 0;
-  my $total_wholesale = 0;
-  for my $item (@$items) {
-    $total += $item->{extended_retailPrice};
-    $total_gst += $item->{extended_gst};
-    $total_wholesale += $item->{extended_wholesale};
+  my $cart = $req->cart($how);
+
+  if ($cart->is_empty) {
+    $$rmsg = "Your cart is empty";
+    return;
   }
-  $values->{total} = $total;
-  $values->{gst} = $total_gst;
-  $values->{wholesaleTotal} = $total_wholesale;
 
+  # FIXME? this doesn't take discounting into effect
+  $values->{gst} = $cart->gst;
+  $values->{wholesaleTotal} = $cart->wholesaleTotal;
+
+  my $items = $cart->items;
+  my $products = $cart->products;
   my $prompt_ship = $cfg->entry("shop", "shipping", 0);
   if ($prompt_ship) {
     if (_any_physical_products($products)) {
@@ -1818,8 +1825,8 @@ sub _fillout_order {
        $values->{shipping_name} = $courier->name;
        $values->{shipping_cost} = $cost;
        $values->{shipping_trace} = $courier->trace;
+       $cart->set_shipping_cost($cost);
        #$values->{delivery_in} = $courier->delivery_in();
-       $values->{total} += $values->{shipping_cost};
       }
       else {
        # XXX: What to do?
@@ -1834,6 +1841,14 @@ sub _fillout_order {
       $values->{shipping_trace} = "All products have zero weight.";
     }
   }
+  if ($cart->coupon_active) {
+    $values->{coupon_code} = $cart->coupon_code;
+  }
+  else {
+    $values->{coupon_code} = "";
+  }
+  $values->{coupon_code_discount_pc} = $cart->coupon_code_discount_pc;
+  $values->{total} = $cart->total;
 
   my $cust_class = custom_class($cfg);
 
index 97ddd26..9c29728 100644 (file)
   </table>
   <table width="100%" border="0" cellspacing="0" cellpadding="0">
     <tr>
-      <td>Coupon code: <input type="text" name="coupon" value="<:= cart.coupon_code -:>">
+      <td colspan="2">Coupon code: <input type="text" name="coupon" value="<:= cart.coupon_code -:>">
 <:.if cart.coupon_active -:>
 Coupon active
 <:.elsif cart.coupon_valid -:>
 <:.if request.siteuser -:>
 Your cart contains items the code isn't valid for
 <:.else -:>
-You need to logon
+You need to logon, or your cart contains items the code isn't valid for.
 <:.end if -:>
 <:.elsif cart.coupon_code ne "" -:>
 Unknown coupon code
 <:.end if -:>
 </td>
 <:.if cart.coupon_active -:>
-      <td height="20">&nbsp;</td>
       <td height="20" bgcolor="#666666">&nbsp;</td>
       <td align="CENTER" height="20" bgcolor="#666666" NOWRAP><font size="2" face="Verdana, Arial, Helvetica, sans-serif" color="#FFFFFF"> 
         <b>DISCOUNT</b></font></td>
       <td height="20" bgcolor="#666666">&nbsp;</td>
 <:.else -:>
-      <td colspan="6"></td>
+      <td colspan="5"></td>
 <:.end if -:>
     </tr>
 <:.if cart.coupon_active -:>
index cda747a..cf3aef6 100644 (file)
   </tr>
 </table>
 <table width="100%" border="0" cellspacing="0" cellpadding="0">    
+<:.if order.coupon_code ne "" -:>
+    <tr>
+      <td>Coupon code: <:= order.coupon_code -:></td>
+      <td height="20">&nbsp;</td>
+      <td height="20" bgcolor="#666666">&nbsp;</td>
+      <td align="CENTER" height="20" bgcolor="#666666" NOWRAP><font size="2" face="Verdana, Arial, Helvetica, sans-serif" color="#FFFFFF"> 
+        <b>DISCOUNT</b></font></td>
+      <td height="20" bgcolor="#666666">&nbsp;</td>
+    </tr>
+    <tr>
+      <td colspan="2">&nbsp;</td>
+      <td height="20" style="border-left: 1px solid #666666">&nbsp;</td>
+      <td align="CENTER">$<:= bse.number("money", order.product_cost_discount) -:></td>
+      <td height="20" style="border-right: 1px solid #666666">&nbsp;</td>
+    </tr>
+
+<:.end if -:>
   <tr> 
     <td>&nbsp;</td>
     <td height="20">&nbsp;</td>
index 40c3055..0755766 100644 (file)
@@ -67,6 +67,7 @@ function BSE_validateForm {
   safe with us.<br>
 </p>
 </font> 
+<form action="<:dyntarget shop:>" method="post">
 <table width="100%" border="0" cellspacing="0" cellpadding="0">
   <tr> 
     <td align="center" bgcolor="#CCCCCC" width="100%" height="18"> <font size="2" face="Verdana, Arial, Helvetica, sans-serif"> 
@@ -199,7 +200,6 @@ Unknown coupon code
     </table>
     <br>
 <:eif User:>
-<form action="<:dyntarget shop:>" method="POST" onSubmit="BSE_validateForm();return document.MM_returnValue">
   <font face="Verdana, Arial, Helvetica, sans-serif" size="3"> <b>Billing Details:</b></font> 
   <hr noshade size="1">
   <table border="0" cellspacing="0" cellpadding="0">