move a lot of cart logic to the cart object
authorTony Cook <tony@develop-help.com>
Tue, 4 Jun 2013 00:24:27 +0000 (10:24 +1000)
committerTony Cook <tony@develop-help.com>
Sun, 21 Jul 2013 23:34:15 +0000 (09:34 +1000)
still more to go though

site/cgi-bin/modules/BSE/Cart.pm
site/cgi-bin/modules/BSE/Shop/Util.pm
site/cgi-bin/modules/BSE/TB/OrderItem.pm
site/cgi-bin/modules/BSE/UI/Shop.pm

index 4636ccc..fa69872 100644 (file)
@@ -2,7 +2,7 @@ package BSE::Cart;
 use strict;
 use Scalar::Util;
 
-our $VERSION = "1.002";
+our $VERSION = "1.003";
 
 =head1 NAME
 
@@ -41,6 +41,7 @@ sub new {
      products => {},
      req => $req,
      stage => $stage,
+     shipping => 0,
     }, $class;
   Scalar::Util::weaken($self->{req});
   my $items = $req->session->{cart} || [];
@@ -49,9 +50,29 @@ sub new {
   my $index = 0;
   $self->{items} =  [ map BSE::Cart::Item->new($_, $index++, $self), @$items ];
 
+  if ($stage eq 'cart' || $stage eq 'checkout') {
+    $self->_enter_cart;
+  }
+
   return $self;
 }
 
+sub _enter_cart {
+  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->enter_cart($self->items, $self->products,
+                         \%custom_state, $req->cfg);
+}
+
 =item items()
 
 Return an array reference of cart items.
@@ -59,7 +80,7 @@ Return an array reference of cart items.
 =cut
 
 sub items {
-  return $_[0]{items};
+  return wantarray ? @{$_[0]{items}} : $_[0]{items};
 }
 
 =item products().
@@ -71,7 +92,10 @@ the array reference returned by items().
 
 sub products {
   my $self = shift;
-  return [ map $self->_product($_->{productId}), @{$self->items} ];
+
+  my @products = map $self->_product($_->{productId}), @{$self->items};
+
+  return wantarray ? @products : \@products;
 }
 
 =item total_cost
@@ -85,12 +109,38 @@ sub total_cost {
 
   my $total_cost = 0;
   for my $item (@{$self->items}) {
-    $total_cost += $item->{extended};
+    $total_cost += $item->extended_retailPrice;
   }
 
   return $total_cost;
 }
 
+=item set_shipping_cost()
+
+Set the cost of shipping.
+
+Called by the shop.
+
+=cut
+
+sub set_shipping_cost {
+  my ($self, $cost) = @_;
+
+  $self->{shipping} = $cost;
+}
+
+=item shipping_cost()
+
+Fetch the cost of shipping.
+
+=cut
+
+sub shipping_cost {
+  my ($self) = @_;
+
+  return $self->{shipping};
+}
+
 =item total_units
 
 Return the total number of units in the cart.
@@ -110,14 +160,208 @@ sub total_units {
 
 =item total
 
-Total of items in the cart and any custom extras.
+Total of items in the cart and shipping costs.
+
+This doesn't handle custom costs yet.
 
 =cut
 
 sub total {
   my ($self) = @_;
 
-  "FIXME";
+  return $self->total_cost() + $self->shipping_cost();
+}
+
+=item have_sales_files
+
+Return true if the cart contains products with files that are for
+sale.
+
+=cut
+
+sub have_sales_files {
+  my ($self) = @_;
+
+  unless (defined $self->{have_sales_files}) {
+    $self->{have_sales_files} = 0;
+  PRODUCTS:
+    for my $prod (@{$self->products}) {
+      if ($prod->has_sales_files) {
+       $self->{have_sales_files} = 1;
+       last PRODUCTS;
+      }
+    }
+  }
+
+  return $self->{have_sales_files};
+}
+
+=item need_logon
+
+Return true if the cart contains items that the user needs to be
+logged on to purchase, or if the current user isn't qualified to
+purchase the item.
+
+Call need_logon_message() to get the reason for this method returning
+false.
+
+=cut
+
+sub need_logon {
+  my ($self) = @_;
+
+  unless (exists $self->{need_logon}) {
+    $self->{need_logon} = $self->_need_logon;
+  }
+
+  $self->{need_logon} or return;
+
+  return 1;
+}
+
+=head1 need_logon_message
+
+Returns a list with the error message and message id of the reason the
+user needs to logon for this cart.
+
+=cut
+
+sub need_logon_message {
+  my ($self) = @_;
+
+  unless (exists $self->{need_logon}) {
+    $self->{need_logon} = $self->_need_logon;
+  }
+
+  return @{$self->{logon_reason}};
+}
+
+=item custom_state
+
+State managed by a custom class.
+
+=cut
+
+sub custom_state {
+  my ($self) = @_;
+
+  $self->{custom_state};
+}
+
+=item affiliate_code
+
+Return the stored affiliate code.
+
+=cut
+
+sub affiliate_code {
+  my ($self) = @_;
+
+  my $code = $self->{req}->session->{affiliate_code};
+  defined $code or $code = '';
+
+  return $code;
+}
+
+=item any_physcial_products
+
+Returns true if the cart contains any physical products, ie. needs
+shipping.
+
+=cut
+
+sub any_physical_products {
+  my ($self) = @_;
+
+  for my $prod (@{$self->products}) {
+    if ($prod->weight) {
+      return 1;
+      last;
+    }
+  }
+
+  return 0;
+}
+
+
+=item _need_logon
+
+Internal implementation of need_logon.
+
+=cut
+
+sub _need_logon {
+  my ($self) = @_;
+
+  my $cfg = $self->{req}->cfg;
+
+  $self->{logon_reason} = [];
+
+  my $reg_if_files = $cfg->entryBool('shop', 'register_if_files', 1);
+
+  my $user = $self->{req}->siteuser;
+
+  if (!$user && $reg_if_files) {
+    require BSE::TB::ArticleFiles;
+    # scan to see if any of the products have files
+    # requires a subscription or subscribes
+    for my $prod (@{$self->products}) {
+      my @files = $prod->files;
+      if (grep $_->forSale, @files) {
+       $self->{logon_reason} =
+         [ "register before checkout", "shop/fileitems" ];
+       return;
+      }
+      if ($prod->{subscription_id} != -1) {
+       $self->{logon_reason} =
+         [ "you must be logged in to purchase a subscription", "shop/buysub" ];
+       return;
+      }
+      if ($prod->{subscription_required} != -1) {
+       $self->{logon_reason} = 
+         [ "must be logged in to purchase a product requiring a subscription", "shop/subrequired" ];
+       return;
+      }
+    }
+  }
+
+  my $require_logon = $cfg->entryBool('shop', 'require_logon', 0);
+  if (!$user && $require_logon) {
+    $self->{logon_reason} =
+      [ "register before checkout", "shop/logonrequired" ];
+    return;
+  }
+
+  # check the user has the right required subs
+  # and that they qualify to subscribe for limited subscription products
+  if ($user) {
+    for my $prod (@{$self->products}) {
+      my $sub = $prod->subscription_required;
+      if ($sub && !$user->subscribed_to($sub)) {
+       $self->{logon_reason} =
+         [ "you must be subscribed to $sub->{title} to purchase one of these products", "shop/subrequired" ];
+       return;
+      }
+
+      $sub = $prod->subscription;
+      if ($sub && $prod->is_renew_sub_only) {
+       unless ($user->subscribed_to_grace($sub)) {
+         $self->{logon_reason} =
+           [ "you must be subscribed to $sub->{title} to use this renew only product", "sub/renewsubonly" ];
+         return;
+       }
+      }
+      if ($sub && $prod->is_start_sub_only) {
+       if ($user->subscribed_to_grace($sub)) {
+         $self->{logon_reason} =
+           [ "you must not be subscribed to $sub->{title} already to use this new subscription only product", "sub/newsubonly" ];
+         return;
+       }
+      }
+    }
+  }
+  
+  return;
 }
 
 sub _product {
@@ -239,7 +483,7 @@ sub extended {
   $base =~ /^(price|retailPrice|gst|wholesalePrice)$/
     or return 0;
 
-  return self->$base() * $self->{units};
+  return $self->$base() * $self->{units};
 }
 
 sub extended_retailPrice {
@@ -379,6 +623,24 @@ sub AUTOLOAD {
   }
 }
 
+=item description
+
+=item title
+
+=cut
+
+sub description {
+  my ($self) = @_;
+
+  $self->product->description;
+}
+
+sub title {
+  my ($self) = @_;
+
+  $self->product->title;
+}
+
 1;
 
 =back
index bfcd1bd..e90bb3d 100644 (file)
@@ -3,11 +3,11 @@ use strict;
 use vars qw(@ISA @EXPORT_OK);
 @ISA = qw/Exporter/;
 @EXPORT_OK = qw/shop_cart_tags cart_item_opts nice_options shop_nice_options
-                total shop_total load_order_fields basic_tags need_logon
+                total shop_total load_order_fields basic_tags
                 payment_types order_item_opts
  PAYMENT_CC PAYMENT_CHEQUE PAYMENT_CALLME PAYMENT_MANUAL PAYMENT_PAYPAL/;
 
-our $VERSION = "1.007";
+our $VERSION = "1.008";
 
 our %EXPORT_TAGS =
   (
@@ -21,8 +21,9 @@ use BSE::CfgInfo qw(custom_class);
 use Carp 'confess';
 use BSE::Util::HTML qw(escape_html);
 use BSE::Shop::PaymentTypes qw(:default payment_types);
+use BSE::Util::Iterate;
 
-=item shop_cart_tags($acts, $cart, $cart_prods, $req, $stage)
+=item shop_cart_tags($cart, $stage)
 
 Returns a list of tags which display the cart details
 
@@ -44,96 +45,56 @@ And several more undocumented DOCME.
 =cut
 
 sub shop_cart_tags {
-  my ($acts, $cart, $cart_prods, $req, $stage) = @_;
+  my ($acts, $cart, $req, $stage) = @_;
 
   my $cfg = $req->cfg;
   my $q = $req->cgi;
   $cfg or confess "No config";
   $cfg->isa("BSE::Cfg") or confess "Not a config";
 
-  my $item_index;
-  my $option_index;
-  my @options;
-  my $sem_session;
   my $location;
-  my $item;
-  my $product;
-  my $have_sale_files;
+  my $current_item;
+  my $ito = BSE::Util::Iterate::Objects->new;
   return
     (
      $req->dyn_user_tags(),
-     iterate_items_reset => sub { $item_index = -1 },
-     iterate_items => 
-     sub { 
-       if (++$item_index < @$cart) {
-        $option_index = -1;
-        @options = cart_item_opts($req,
-                                  $cart->[$item_index], 
-                                  $cart_prods->[$item_index]);
-        undef $sem_session;
-        undef $location;
-        $item = $cart->[$item_index];
-        $product = $cart_prods->[$item_index];
-        return 1;
-       }
-       undef $item;
-       undef $sem_session;
-       undef $product;
-       undef $location;
-       return 0;
-     },
-     item => 
-     sub { 
-       if ($_[0] eq "link") {
-        my $value = $product->link;
-        unless ($value =~ /^\w+:/) {
-          $value = $req->cfg->entryErr('site', 'url') . $value;
-        }
-        return escape_html($value);
-       }
-       elsif (defined $item->{$_[0]}) {
-        return escape_html($item->{$_[0]});
-       }
-       else {
-        return tag_article($product, $cfg, $_[0]);
-       }
-     },
+     $ito->make
+     (
+      plural => "items",
+      single => "item",
+      code => sub { @{$cart->items} },
+      store => \$current_item,
+     ),
+     count => scalar(@{$cart->items}),
      extended =>
      sub { 
        my $what = $_[0] || 'retailPrice';
-       $cart->[$item_index]{units} * $cart_prods->[$item_index]{$what};
+       $current_item->extended($what);
      },
-     index => sub { $item_index },
-     total => 
-     sub { total($cart, $cart_prods, $req->session->{custom}, $cfg, $stage) },
-     count => sub { scalar @$cart },
-     iterate_options_reset => sub { $option_index = -1 },
-     iterate_options => sub { ++$option_index < @options },
-     option => sub { escape_html($options[$option_index]{$_[0]}) },
-     ifOptions => sub { @options },
-     options => sub { nice_options(@options) },
-     session => [ \&tag_session, \$item, \$sem_session ],
-     location => [ \&tag_location, \$item, \$location ],
-     ifHaveSaleFiles => [ \&tag_ifHaveSaleFiles, \$have_sale_files, $cart_prods ],
+     total => sub { $cart->total_cost },
+     $ito->make
+     (
+      plural => "options",
+      single => "option",
+     ),
+     options => sub { $current_item->option_text },
+     session => [ \&tag_session, \$current_item ],
+     location => [ \&tag_location, \$current_item, \$location ],
+     ifHaveSaleFiles => [ have_sales_files => $cart ],
      custom_class($cfg)
-     ->checkout_actions($acts, $cart, $cart_prods, $req->session->{custom}, $q, $cfg),
+     ->checkout_actions($acts, $cart->items, $cart->products, $req->session->{custom}, $q, $cfg),
     );  
 }
 
 sub tag_session {
-  my ($ritem, $rsession, $arg) = @_;
+  my ($ritem, $arg) = @_;
 
   $$ritem or return '';
 
   $$ritem->{session_id} or return '';
 
-  unless ($$rsession) {
-    require BSE::TB::SeminarSessions;
-    $$rsession = BSE::TB::SeminarSessions->getByPkey($$ritem->{session_id})
-      or return '';
-  }
-
-  my $value = $$rsession->{$arg};
+  my $session = $$ritem->session;
+  my $value = $session->{$arg};
   defined $value or return '';
 
   escape_html($value);
@@ -363,64 +324,4 @@ sub basic_tags {
     );
 }
 
-sub need_logon {
-  my ($req, $cart, $cart_prods) = @_;
-
-  defined $req or confess "No cgi parameter supplied";
-  my $cfg = $req->cfg;
-  my $cgi = $req->cgi;
-  
-  my $reg_if_files = $cfg->entryBool('shop', 'register_if_files', 1);
-
-  my $user = $req->siteuser;
-
-  if (!$user && $reg_if_files) {
-    require BSE::TB::ArticleFiles;
-    # scan to see if any of the products have files
-    # requires a subscription or subscribes
-    for my $prod (@$cart_prods) {
-      my @files = BSE::TB::ArticleFiles->getBy(articleId=>$prod->{id});
-      if (grep $_->{forSale}, @files) {
-       return ("register before checkout", "shop/fileitems");
-      }
-      if ($prod->{subscription_id} != -1) {
-       return ("you must be logged in to purchase a subscription", "shop/buysub");
-      }
-      if ($prod->{subscription_required} != -1) {
-       return ("must be logged in to purchase a product requiring a subscription", "shop/subrequired");
-      }
-    }
-  }
-
-  my $require_logon = $cfg->entryBool('shop', 'require_logon', 0);
-  if (!$user && $require_logon) {
-    return ("register before checkout", "shop/logonrequired");
-  }
-
-  # check the user has the right required subs
-  # and that they qualify to subscribe for limited subscription products
-  if ($user) {
-    for my $prod (@$cart_prods) {
-      my $sub = $prod->subscription_required;
-      if ($sub && !$user->subscribed_to($sub)) {
-       return ("you must be subscribed to $sub->{title} to purchase one of these products", "shop/subrequired");
-      }
-
-      $sub = $prod->subscription;
-      if ($sub && $prod->is_renew_sub_only) {
-       unless ($user->subscribed_to_grace($sub)) {
-         return ("you must be subscribed to $sub->{title} to use this renew only product", "sub/renewsubonly");
-       }
-      }
-      if ($sub && $prod->is_start_sub_only) {
-       if ($user->subscribed_to_grace($sub)) {
-         return ("you must not be subscribed to $sub->{title} already to use this new subscription only product", "sub/newsubonly");
-       }
-      }
-    }
-  }
-  
-  return;
-}
-
 1;
index 73b9dae..492212a 100644 (file)
@@ -5,7 +5,7 @@ use Squirrel::Row;
 use vars qw/@ISA/;
 @ISA = qw/Squirrel::Row/;
 
-our $VERSION = "1.002";
+our $VERSION = "1.003";
 
 sub columns {
   return qw/id productId orderId units price wholesalePrice gst options
@@ -90,4 +90,15 @@ sub session {
   return BSE::TB::SeminarSessions->getByPkey($self->session_id);
 }
 
+# cart item compatibility
+sub retailPrice {
+  $_[0]->price;
+}
+
+sub extended {
+  my ($self, $name) = @_;
+
+  return $self->units * $self->$name();
+}
+
 1;
index 903e87d..92e2710 100644 (file)
@@ -3,7 +3,7 @@ use strict;
 use base 'BSE::UI::Dispatch';
 use BSE::Util::HTML qw(:default popup_menu);
 use BSE::Util::SQL qw(now_sqldate now_sqldatetime);
-use BSE::Shop::Util qw(:payment need_logon shop_cart_tags payment_types nice_options 
+use BSE::Shop::Util qw(:payment shop_cart_tags payment_types nice_options 
                        cart_item_opts basic_tags order_item_opts);
 use BSE::CfgInfo qw(custom_class credit_card_class bse_default_country);
 use BSE::TB::Orders;
@@ -16,8 +16,9 @@ use Digest::MD5 'md5_hex';
 use BSE::Shipping;
 use BSE::Countries qw(bse_country_code);
 use BSE::Util::Secure qw(make_secret);
+use BSE::Template;
 
-our $VERSION = "1.037";
+our $VERSION = "1.038";
 
 use constant MSG_SHOP_CART_FULL => 'Your shopping cart is full, please remove an item and try adding an item again';
 
@@ -96,6 +97,7 @@ sub req_cart {
   my @options;
   my $option_index;
   
+  my $cart = $req->cart("cart");
   $req->session->{custom} ||= {};
   my %custom_state = %{$req->session->{custom}};
 
@@ -109,13 +111,14 @@ sub req_cart {
   my %acts;
   %acts =
     (
-     $cust_class->cart_actions(\%acts, \@cart, \@cart_prods, \%custom_state, 
-                              $req->cfg),
-     shop_cart_tags(\%acts, \@items, \@cart_prods, $req, 'cart'),
+     $cust_class->cart_actions(\%acts, $cart->items, $cart->products,
+                              $cart->custom_state, $req->cfg),
+     shop_cart_tags(\%acts, $cart, $req, 'cart'),
      basic_tags(\%acts),
      msg => $msg,
     );
-  $req->session->{custom} = \%custom_state;
+
+  $req->session->{custom} = { %{$cart->custom_state} };
   $req->session->{order_info_confirmed} = 0;
 
   # intended to ajax enable the shop cart with partial templates
@@ -506,29 +509,21 @@ sub req_checkout {
   my $need_delivery = ( $olddata ? $cgi->param("need_delivery") : $req->session->{order_need_delivery} ) || 0;
 
   $class->update_quantities($req);
-  my @cart = @{$req->session->{cart}};
+  my $cart = $req->cart("checkout");
+  my @cart = @{$cart->items};
 
   @cart or return $class->req_cart($req);
 
-  my @cart_prods;
-  my @items = $class->_build_items($req, \@cart_prods);
+  my @cart_prods = @{$cart->products};
+  my @items = @{$cart->items};
 
-  if (my ($msg, $id) = $class->_need_logon($req, \@cart, \@cart_prods)) {
+  if ($cart->need_logon) {
+    my ($msg, $id) = $cart->need_logon_reason;
     return $class->_refresh_logon($req, $msg, $id);
-    return;
   }
 
   my $user = $req->siteuser;
 
-  $req->session->{custom} ||= {};
-  my %custom_state = %{$req->session->{custom}};
-
-  my $cust_class = custom_class($cfg);
-  $cust_class->enter_cart(\@cart, \@cart_prods, \%custom_state, $cfg);
-
-  my $affiliate_code = $req->session->{affiliate_code};
-  defined $affiliate_code or $affiliate_code = '';
-
   my $order_info = $req->session->{order_info};
 
   my $billing_map = BSE::TB::Order->billing_to_delivery_map;
@@ -566,7 +561,7 @@ sub req_checkout {
   my $shipping_name = '';
   my $prompt_ship = $cfg->entry("shop", "shipping", 0);
 
-  my $physical = _any_physical_products(\@cart_prods);
+  my $physical = $cart->any_physical_products;
 
   if ($prompt_ship) {
     my $sel_cn = $old->("shipping_name") || "";
@@ -656,9 +651,12 @@ sub req_checkout {
     }
   }
 
+  my $cust_class = custom_class($cfg);
+
   if (!$message && keys %$errors) {
     $message = $req->message($errors);
   }
+  $cart->set_shipping_cost($shipping_cost);
 
   my $item_index = -1;
   my @options;
@@ -666,16 +664,16 @@ sub req_checkout {
   my %acts;
   %acts =
     (
-     shop_cart_tags(\%acts, \@items, \@cart_prods, $req, 'checkout'),
+     shop_cart_tags(\%acts, $cart, $req, 'checkout'),
      basic_tags(\%acts),
      message => $message,
      msg => $message,
      old => sub { escape_html($old->($_[0])); },
      $cust_class->checkout_actions(\%acts, \@cart, \@cart_prods, 
-                                  \%custom_state, $req->cgi, $cfg),
+                                  $cart->custom_state, $req->cgi, $cfg),
      ifUser => [ \&tag_ifUser, $user ],
      user => $user ? [ \&tag_hash, $user ] : '',
-     affiliate_code => escape_html($affiliate_code),
+     affiliate_code => escape_html($cart->affiliate_code),
      error_img => [ \&tag_error_img, $cfg, $errors ],
      ifShipping => $prompt_ship,
      shipping_select => $shipping_select,
@@ -687,7 +685,7 @@ sub req_checkout {
      ifPhysical => $physical,
      ifNeedDelivery => $need_delivery,
     );
-  $req->session->{custom} = \%custom_state;
+  $req->session->{custom} = $cart->custom_state;
   my $tmp = $acts{total};
   $acts{total} =
     sub {
@@ -786,11 +784,14 @@ sub req_order {
   $class->_validate_cfg($req, \$msg)
     or return $class->req_cart($req, $msg);
 
-  my @products;
-  my @items = $class->_build_items($req, \@products);
+  my $cart = $req->cart("order");
+
+  my @products = @{$cart->products};
+  my @items = @{$cart->items};
 
   my $id;
-  if (($msg, $id) = $class->_need_logon($req, \@items, \@products)) {
+  if ($cart->need_logon) {
+    my ($msg, $id) = $cart->need_logon_message;
     return $class->_refresh_logon($req, $msg, $id);
   }
 
@@ -861,39 +862,39 @@ sub req_show_payment {
 
   # ideally supply order_id to be consistent with a_payment.
   my $order_id = $cgi->param('orderid') || $cgi->param("order_id");
+  my $cart;
   if ($order_id) {
     $order_id =~ /^\d+$/
       or return $class->req_cart($req, "No or invalid order id supplied");
-    
+
     my $user = $req->siteuser
       or return $class->_refresh_logon
        ($req, "Please logon before paying your existing order", "logonpayorder",
         undef, { a_show_payment => 1, orderid => $order_id });
-    
+
     require BSE::TB::Orders;
     $order = BSE::TB::Orders->getByPkey($order_id)
       or return $class->req_cart($req, "Unknown order id");
-    
+
     $order->siteuser_id == $user->id
       or return $class->req_cart($req, "You can only pay for your own orders");
-    
+
     $order->paidFor
       and return $class->req_cart($req, "Order $order->{id} has been paid");
-    
-    @items = $order->items;
-    @products = $order->products;
+
+    $cart = $order;
   }
   else {
     $req->session->{order_info_confirmed}
       or return $class->req_checkout($req, 'Please proceed via the checkout page');
-    
+
     $req->session->{cart} && @{$req->session->{cart}}
       or return $class->req_cart($req, "Your cart is empty");
-    
+
     $order = $req->session->{order_info}
       or return $class->req_checkout($req, "You need to enter order information first");
 
-    @items = $class->_build_items($req, \@products);
+    $cart = $req->cart("payment");
   }
 
   $errors ||= {};
@@ -916,13 +917,13 @@ sub req_show_payment {
      message => $msg,
      msg => $msg,
      order => [ \&tag_hash, $order ],
-     shop_cart_tags(\%acts, \@items, \@products, $req, 'payment'),
+     shop_cart_tags(\%acts, $cart, $req, 'payment'),
      ifMultPaymentTypes => @payment_types > 1,
      checkedPayment => [ \&tag_checkedPayment, $payment, \%types_by_name ],
      ifPayments => [ \&tag_ifPayments, \@payment_types, \%types_by_name ],
      paymentTypeId => [ \&tag_paymentTypeId, \%types_by_name ],
      error_img => [ \&tag_error_img, $cfg, $errors ],
-     total => $order->{total},
+     total => $cart->total,
      delivery_in => $order->{delivery_in},
      shipping_cost => $order->{shipping_cost},
      shipping_method => $order->{shipping_method},
@@ -935,6 +936,8 @@ sub req_show_payment {
     $acts{"checkedIfFirst$name"} = $payment_types[0] == $id ? "checked " : "";
     $acts{"checkedPayment$name"} = $payment == $id ? 'checked="checked" ' : "";
   }
+  $req->set_variable(ordercart => $cart);
+  $req->set_variable(order => $order);
 
   return $req->response('checkoutpay', \%acts);
 }
@@ -1654,12 +1657,6 @@ sub _refresh_logon {
   return BSE::Template->get_refresh($url, $req->cfg);
 }
 
-sub _need_logon {
-  my ($class, $req, $cart, $cart_prods) = @_;
-
-  return need_logon($req, $cart, $cart_prods);
-}
-
 sub tag_checkedPayment {
   my ($payment, $types_by_name, $args) = @_;
 
@@ -1704,6 +1701,10 @@ sub update_quantities {
       }
     }
   }
+  my ($coupon) = $cgi->param("coupon");
+  if (defined $coupon) {
+    $session->{cart_coupon} = $coupon;
+  }
   @cart = grep { $_->{units} != 0 } @cart;
   $session->{cart} = \@cart;
   $session->{custom} ||= {};