mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2009-04-26 07:47 pm

[dw-free] Finishing up payment system

[commit: http://hg.dwscoalition.org/dw-free/rev/9cc8ea02116c]

http://bugs.dwscoalition.org/show_bug.cgi?id=116

Lots of updates for the payment system. Now has receipts, order history.

Patch by [personal profile] janinedog.

Files modified:
  • bin/upgrading/en.dat
  • bin/upgrading/update-db-general.pl
  • cgi-bin/DW/Logic/MenuNav.pm
  • cgi-bin/DW/Shop.pm
  • cgi-bin/DW/Shop/Cart.pm
  • cgi-bin/DW/Shop/Engine/CheckMoneyOrder.pm
  • cgi-bin/DW/Shop/Engine/PayPal.pm
  • cgi-bin/LJ/Setting/Display/Orders.pm
  • doc/config-local.pl.txt
  • htdocs/manage/settings/index.bml
  • htdocs/shop.bml
  • htdocs/shop.bml.text
  • htdocs/shop/cancel.bml
  • htdocs/shop/cancel.bml.text
  • htdocs/shop/checkout.bml
  • htdocs/shop/checkout.bml.text
  • htdocs/shop/cmo_confirm.bml
  • htdocs/shop/confirm.bml
  • htdocs/shop/confirm.bml.text
  • htdocs/shop/history.bml
  • htdocs/shop/history.bml.text
  • htdocs/shop/pp_confirm.bml
  • htdocs/shop/receipt.bml
  • htdocs/shop/receipt.bml.text
--------------------------------------------------------------------------------
diff -r 3f7acfee7d4d -r 9cc8ea02116c bin/upgrading/en.dat
--- a/bin/upgrading/en.dat	Sun Apr 26 19:12:29 2009 +0000
+++ b/bin/upgrading/en.dat	Sun Apr 26 19:47:15 2009 +0000
@@ -2187,6 +2187,10 @@ menunav.read.syndicatedfeeds=Syndicated 
 
 menunav.read.tags=Tags
 
+menunav.shop=Shop
+
+menunav.shop.paidtime=Buy a Paid Account
+
 notification_method.email.title=Email
 
 notification_method.im.title=IM
@@ -2581,6 +2585,10 @@ setting.display.logins.label=Logins
 
 setting.display.logins.option=Manage login sessions
 
+setting.display.orders.label=Orders
+
+setting.display.orders.option=View order history
+
 setting.display.password.actionlink=Change
 
 setting.display.password.label=Password
diff -r 3f7acfee7d4d -r 9cc8ea02116c bin/upgrading/update-db-general.pl
--- a/bin/upgrading/update-db-general.pl	Sun Apr 26 19:12:29 2009 +0000
+++ b/bin/upgrading/update-db-general.pl	Sun Apr 26 19:47:15 2009 +0000
@@ -3146,12 +3146,13 @@ register_tablecreate('shop_carts', <<'EO
 register_tablecreate('shop_carts', <<'EOC');
 CREATE TABLE shop_carts (
     cartid INT UNSIGNED NOT NULL,
-    authcode VARCHAR(20) NOT NULL,
     starttime INT UNSIGNED NOT NULL,
     userid INT UNSIGNED,
     uniq VARCHAR(15) NOT NULL,
     state INT UNSIGNED NOT NULL,
+    paymentmethod INT UNSIGNED NOT NULL,
     nextscan INT UNSIGNED NOT NULL DEFAULT 0,
+    authcode VARCHAR(20) NOT NULL,
 
     cartblob MEDIUMBLOB NOT NULL,
 
@@ -4037,6 +4038,11 @@ register_alter(sub {
                   q{ALTER TABLE shop_carts ADD COLUMN authcode VARCHAR(20) NOT NULL AFTER nextscan} );
     }
 
+    unless ( column_type( 'shop_carts', 'paymentmethod' ) =~ /int/ ) {
+        do_alter( 'shop_carts',
+                  q{ALTER TABLE shop_carts ADD COLUMN paymentmethod INT UNSIGNED NOT NULL AFTER state} );
+    }
+
 });
 
 
diff -r 3f7acfee7d4d -r 9cc8ea02116c cgi-bin/DW/Logic/MenuNav.pm
--- a/cgi-bin/DW/Logic/MenuNav.pm	Sun Apr 26 19:12:29 2009 +0000
+++ b/cgi-bin/DW/Logic/MenuNav.pm	Sun Apr 26 19:47:15 2009 +0000
@@ -189,9 +189,9 @@ sub get_menu_navigation {
             name => 'shop',
             items => [
                 {
-                    url => "$LJ::SITEROOT/shop?for=paidtime",
+                    url => "$LJ::SITEROOT/shop",
                     text => "menunav.shop.paidtime",
-                    display => $never,
+                    display => LJ::is_enabled( 'payments' ) ? 1 : 0,
                 },
             ],
         },
diff -r 3f7acfee7d4d -r 9cc8ea02116c cgi-bin/DW/Shop.pm
--- a/cgi-bin/DW/Shop.pm	Sun Apr 26 19:12:29 2009 +0000
+++ b/cgi-bin/DW/Shop.pm	Sun Apr 26 19:47:15 2009 +0000
@@ -65,6 +65,21 @@ our $STATE_CLOSED      = 8;    # carts c
 # any other state transition is hereby considered null and void.
 
 
+# keys are the names of the various payment methods as passed by the cart widget drop-down
+# values are hashrefs with id (the integer value that is stored in the 'paymentmethod'
+# field in the db) and class (the name of the DW::Shop::Engine class)
+our %PAYMENTMETHODS = (
+    paypal => {
+        id => 1,
+        class => 'PayPal',
+    },
+    checkmoneyorder => {
+        id => 2,
+        class => 'CheckMoneyOrder',
+    },
+);
+
+
 # called to return an instance of the shop; auto-determines if we have a
 # remote user and uses that, else, just returns an anonymous shop
 sub get {
diff -r 3f7acfee7d4d -r 9cc8ea02116c cgi-bin/DW/Shop/Cart.pm
--- a/cgi-bin/DW/Shop/Cart.pm	Sun Apr 26 19:12:29 2009 +0000
+++ b/cgi-bin/DW/Shop/Cart.pm	Sun Apr 26 19:47:15 2009 +0000
@@ -7,6 +7,7 @@
 #
 # Authors:
 #      Mark Smith <mark@dreamwidth.org>
+#      Janine Costanzo <janine@netrophic.com>
 #
 # Copyright (c) 2009 by Dreamwidth Studios, LLC.
 #
@@ -61,7 +62,7 @@ sub get {
     my $dbh = LJ::get_db_writer()
         or return undef;
     my $dbcart = $dbh->selectrow_hashref(
-        qq{SELECT userid, cartid, starttime, uniq, state, cartblob, nextscan, authcode
+        qq{SELECT cartblob
            FROM shop_carts
            WHERE $sql AND state = ?
            ORDER BY starttime DESC
@@ -94,7 +95,7 @@ sub get_from_cartid {
     my $dbh = LJ::get_db_writer()
         or return undef;
     my $dbcart = $dbh->selectrow_hashref(
-        qq{SELECT userid, cartid, starttime, uniq, state, cartblob, nextscan, authcode
+        qq{SELECT cartblob
            FROM shop_carts WHERE cartid = ?},
         undef, $cartid
     );
@@ -147,6 +148,7 @@ sub new_cart {
         total     => 0.00,
         nextscan  => 0,
         authcode  => LJ::make_auth_code( 20 ),
+        paymentmethod => 0, # we don't have a payment method yet
     };
 
     # now, delete any old carts we don't need
@@ -169,17 +171,44 @@ sub new_cart {
 }
 
 
+# returns all carts that the given user has ever had
+# can pass 'finished' opt which will omit carts in the OPEN, CLOSED, or
+# CHECKOUT states
+sub get_all {
+    my ( $class, $u, %opts ) = @_;
+    $u = LJ::want_user( $u );
+
+    my $extra_sql = " AND state <> $DW::Shop::STATE_OPEN AND state <> $DW::Shop::STATE_CLOSED AND state <> $DW::Shop::STATE_CHECKOUT"
+        if $opts{finished};
+
+    my $dbh = LJ::get_db_writer()
+        or return undef;
+    my $sth = $dbh->prepare( "SELECT cartblob FROM shop_carts WHERE userid = ?$extra_sql" );
+    $sth->execute( $u->id );
+
+    my @carts = ();
+    while ( my $cart = $sth->fetchrow_hashref ) {
+        push @carts, $class->_build( thaw( $cart->{cartblob} ) );
+    }
+
+    return @carts;
+}
+
+
 # saves the current cart to the database, returns 1/0
 sub save {
     my $self = $_[0];
+
+    # we store the payment method id in the db
+    my $paymentmethod_id = $DW::Shop::PAYMENTMETHODS{$self->paymentmethod}->{id} || 0;
 
     # toss in the database
     my $dbh = LJ::get_db_writer()
         or return undef;
     $dbh->do(
-        q{REPLACE INTO shop_carts (userid, cartid, starttime, uniq, state, nextscan, authcode, cartblob)
-          VALUES (?, ?, ?, ?, ?, ?, ?, ?)},
-        undef, ( map { $self->{$_} } qw/ userid cartid starttime uniq state nextscan authcode / ), nfreeze( $self )
+        q{REPLACE INTO shop_carts (userid, cartid, starttime, uniq, state, nextscan, authcode, paymentmethod, cartblob)
+          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)},
+        undef, ( map { $self->{$_} } qw/ userid cartid starttime uniq state nextscan authcode / ), $paymentmethod_id, nfreeze( $self )
     );
 
     # bail if error
@@ -272,6 +301,20 @@ sub state {
     return $self->{state};
 }
 
+
+# get/set payment method
+sub paymentmethod {
+    my ( $self, $newpaymentmethod ) = @_;
+
+    return $self->{paymentmethod}
+        unless defined $newpaymentmethod;
+
+    $self->{paymentmethod} = $newpaymentmethod;
+    $self->save;
+
+    return $self->{paymentmethod};
+}
+
 ################################################################################
 ## read-only accessor methods
 ################################################################################
@@ -279,6 +322,7 @@ sub state {
 
 sub id       { $_[0]->{cartid}             } 
 sub userid   { $_[0]->{userid}             }
+sub starttime{ $_[0]->{starttime}          }
 sub age      { time() - $_[0]->{starttime} }
 sub items    { $_[0]->{items} ||= []       }
 sub uniq     { $_[0]->{uniq}               }
diff -r 3f7acfee7d4d -r 9cc8ea02116c cgi-bin/DW/Shop/Engine/CheckMoneyOrder.pm
--- a/cgi-bin/DW/Shop/Engine/CheckMoneyOrder.pm	Sun Apr 26 19:12:29 2009 +0000
+++ b/cgi-bin/DW/Shop/Engine/CheckMoneyOrder.pm	Sun Apr 26 19:47:15 2009 +0000
@@ -6,6 +6,7 @@
 #
 # Authors:
 #      Mark Smith <mark@dreamwidth.org>
+#      Janine Costanzo <janine@netrophic.com>
 #
 # Copyright (c) 2009 by Dreamwidth Studios, LLC.
 #
@@ -59,7 +60,7 @@ sub checkout_url {
 
     # the cart is in a good state, so just send them to the confirmation page which
     # gives them instructions on where to send it
-    return "$LJ::SITEROOT/shop/cmo_confirm";
+    return "$LJ::SITEROOT/shop/confirm?ordernum=" . $cart->ordernum;
 }
 
 
@@ -76,7 +77,26 @@ sub confirm_order {
 
     # now set it pending
     $self->cart->state( $DW::Shop::STATE_PEND_PAID );
+
+    # delete cart from memcache
+    my $u = LJ::load_userid( $self->cart->userid );
+    $u->memc_delete( 'cart' ) if LJ::isu( $u );
+
     return 2;
+}
+
+
+# cancel_order()
+#
+# cancels the order, but all it has to do is check the cart state
+sub cancel_order {
+    my $self = $_[0];
+
+    # ensure the cart is in open state
+    return $self->error( 'paypal.engbadstate' )
+        unless $self->cart->state == $DW::Shop::STATE_OPEN;
+
+    return 1;
 }
 
 
diff -r 3f7acfee7d4d -r 9cc8ea02116c cgi-bin/DW/Shop/Engine/PayPal.pm
--- a/cgi-bin/DW/Shop/Engine/PayPal.pm	Sun Apr 26 19:12:29 2009 +0000
+++ b/cgi-bin/DW/Shop/Engine/PayPal.pm	Sun Apr 26 19:47:15 2009 +0000
@@ -125,8 +125,8 @@ sub checkout_url {
         allownote     => 0,
 
         # where PayPal can send people back to
-        cancelurl     => "$LJ::SITEROOT/shop/pp_cancel",
-        returnurl     => "$LJ::SITEROOT/shop/pp_confirm",
+        cancelurl     => "$LJ::SITEROOT/shop/cancel",
+        returnurl     => "$LJ::SITEROOT/shop/confirm",
 
         # custom data we send to reference this cart
         custom        => join( ';', ( $cart->ordernum, $cart->display_total ) ),
@@ -233,15 +233,50 @@ sub confirm_order {
     warn "Failure to save pp_trans: " . $dbh->errstr . "\n"
         if $dbh->err;
 
+    my $u = LJ::load_userid( $self->cart->userid );
+
     # if this order is Complete (i.e., we have the money) then we note that
     if ( $res->{paymentstatus} eq 'Completed' ) {
         $self->cart->state( $DW::Shop::STATE_PAID );
+
+        # delete cart from memcache
+        $u->memc_delete( 'cart' ) if LJ::isu( $u );
+
         return 1;
     }
 
     # okay, so it's pending... sad days
     $self->cart->state( $DW::Shop::STATE_PEND_PAID );
+
+    # delete cart from memcache
+    $u->memc_delete( 'cart' ) if LJ::isu( $u );
+
     return 2;
+}
+
+
+# cancel_order()
+#
+# cancels the order and doesn't send any money
+sub cancel_order {
+    my $self = $_[0];
+
+    # ensure the cart is in open state
+    return $self->error( 'paypal.engbadstate' )
+        unless $self->cart->state == $DW::Shop::STATE_OPEN;
+
+    # ensure we have db
+    my $dbh = DW::Pay::get_db_writer()
+        or return $self->temp_error( 'nodb' );
+
+    $dbh->do(
+        q{DELETE FROM pp_tokens WHERE token = ?},
+        undef, $self->token
+    );
+    return $self->error( 'dberr', errstr => $dbh->errstr )
+        if $dbh->err;
+
+    return 1;
 }
 
 
diff -r 3f7acfee7d4d -r 9cc8ea02116c cgi-bin/LJ/Setting/Display/Orders.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/LJ/Setting/Display/Orders.pm	Sun Apr 26 19:47:15 2009 +0000
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+#
+# LJ::Setting::Display::Orders - shows a link to the user's payment history page
+#
+# Authors:
+#      Janine Costanzo <janine@netrophic.com>
+#
+# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself. For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+
+package LJ::Setting::Display::Orders;
+use base 'LJ::Setting';
+use strict;
+use warnings;
+
+sub should_render {
+    my ($class, $u) = @_;
+
+    return $u && !$u->is_community ? 1 : 0;
+}
+
+sub label {
+    my $class = shift;
+
+    return $class->ml( 'setting.display.orders.label' );
+}
+
+sub option {
+    my ($class, $u, $errs, $args) = @_;
+
+    return "<a href='$LJ::SITEROOT/shop/history'>" . $class->ml( 'setting.display.orders.option' ) . "</a>";
+}
+
+1;
diff -r 3f7acfee7d4d -r 9cc8ea02116c doc/config-local.pl.txt
--- a/doc/config-local.pl.txt	Sun Apr 26 19:12:29 2009 +0000
+++ b/doc/config-local.pl.txt	Sun Apr 26 19:47:15 2009 +0000
@@ -33,6 +33,7 @@
     $SITENAMESHORT = "YourSite";
     $SITENAMEABBREV = "YS";
     $SITECOMPANY = "YourSite's Company";
+    $SITEADDRESS = "123 Main St.<br />Somewhere, XX 12345";
 
     # MemCache information, if you have MemCache servers running
     #@MEMCACHE_SERVERS = ('hostname:port');
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/manage/settings/index.bml
--- a/htdocs/manage/settings/index.bml	Sun Apr 26 19:12:29 2009 +0000
+++ b/htdocs/manage/settings/index.bml	Sun Apr 26 19:47:15 2009 +0000
@@ -121,6 +121,7 @@ body<=
                 LJ::Setting::Display::Logins
                 LJ::Setting::Display::Emails
                 LJ::Setting::Display::EmailPosts
+                LJ::Setting::Display::Orders
             )],
         },
         othersites => {
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop.bml
--- a/htdocs/shop.bml	Sun Apr 26 19:12:29 2009 +0000
+++ b/htdocs/shop.bml	Sun Apr 26 19:47:15 2009 +0000
@@ -39,7 +39,9 @@ body<=
     $ret .= DW::Widget::ShopItemGroupDisplay->render( group => 'paidaccounts' );
 
     $ret .= "<div class='shopbox'>";
-    $ret .= "<p>" . BML::ml( '.sideblurb', { sitename => $LJ::SITENAMESHORT, aopts => "href='$LJ::HELPURL{paidaccountinfo}'" } ). "</p>";
+    $ret .= "<p>" . BML::ml( '.sideblurb', { sitename => $LJ::SITENAMESHORT, aopts => "href='$LJ::HELPURL{paidaccountinfo}'" } ) . "</p>";
+    $ret .= "<p>" . BML::ml( '.viewhistory', { aopts => "href='$LJ::SITEROOT/shop/history.bml'" } ). "</p>"
+        if LJ::get_remote();
     $ret .= "</div>";
 
     return $ret;
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop.bml.text
--- a/htdocs/shop.bml.text	Sun Apr 26 19:12:29 2009 +0000
+++ b/htdocs/shop.bml.text	Sun Apr 26 19:47:15 2009 +0000
@@ -5,3 +5,5 @@
 .sideblurb=You can learn about paid accounts <a [[aopts]]>here</a>.
 
 .title=[[sitename]] Shop
+
+.viewhistory=To view your past orders, go <a [[aopts]]>here</a>.
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/cancel.bml
--- a/htdocs/shop/cancel.bml	Sun Apr 26 19:12:29 2009 +0000
+++ b/htdocs/shop/cancel.bml	Sun Apr 26 19:47:15 2009 +0000
@@ -3,10 +3,10 @@
 #
 # shop/cancel.bml
 #
-# Cancels an outstanding order.
+# This page cancels the given order.
 #
 # Authors:
-#      Mark Smith <mark@dreamwidth.org>
+#      Janine Costanzo <janine@netrophic.com>
 #
 # Copyright (c) 2009 by Dreamwidth Studios, LLC.
 #
@@ -16,7 +16,6 @@
 #
 
 _c?><?page
-title=>Cancel Order
 body<=
 <?_code
 {
@@ -26,8 +25,43 @@ body<=
     return BML::redirect( "$LJ::SITEROOT/" )
         unless LJ::is_enabled( 'payments' );
 
-    # FIXME: cancel order ;)
-    return "FIXME: cancel order";
+    my ( $ordernum, $token, $payerid ) = ( $GET{ordernum}, $GET{token}, $GET{PayerID} );
+    my ( $cart, $eng );
+
+    # use ordernum if we have it, otherwise use token/payerid
+    if ( $ordernum ) {
+        $cart = DW::Shop::Cart->get_from_ordernum( $ordernum );
+        return $ML{'.error.invalidordernum'}
+            unless $cart;
+
+        my $paymentmethod = $cart->paymentmethod;
+        my $paymentmethod_class = 'DW::Shop::Engine::' . $DW::Shop::PAYMENTMETHODS{$paymentmethod}->{class};
+        $eng = $paymentmethod_class->new_from_cart( $cart );
+        return $ML{'.error.invalidcart'}
+            unless $eng;
+    } else {
+        return $ML{'.error.needtoken'}
+            unless $token;
+
+        # we can assume paypal is the engine if we have a token
+        $eng = DW::Shop::Engine::PayPal->new_from_token( $token );
+        return $ML{'.error.invalidtoken'}
+            unless $eng;
+
+        $cart = $eng->cart;
+        $ordernum = $cart->ordernum;
+    }
+
+    # cart must be in open state
+    return BML::redirect( "$LJ::SITEROOT/shop/receipt?ordernum=$ordernum" )
+        unless $cart->state == $DW::Shop::STATE_OPEN;
+
+    # cancel payment and discard cart
+    if ( $eng->cancel_order ) {
+        return BML::redirect( "$LJ::SITEROOT/shop?newcart=1" );
+    }
+
+    return $ML{'.error.cantcancel'};
 }
 _code?>
 <=body
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/cancel.bml.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/cancel.bml.text	Sun Apr 26 19:47:15 2009 +0000
@@ -0,0 +1,11 @@
+;; -*- coding: utf-8 -*-
+
+.error.cantcancel=Sorry, we could not cancel your order at this time.
+
+.error.invalidcart=Your cart is invalid.
+
+.error.invalidordernum=Your order number is invalid.
+
+.error.invalidtoken=Your token is invalid.
+
+.error.needtoken=You did not provide an order number or token.
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/checkout.bml
--- a/htdocs/shop/checkout.bml	Sun Apr 26 19:12:29 2009 +0000
+++ b/htdocs/shop/checkout.bml	Sun Apr 26 19:47:15 2009 +0000
@@ -6,6 +6,7 @@
 #
 # Authors:
 #      Mark Smith <mark@dreamwidth.org>
+#      Janine Costanzo <janine@netrophic.com>
 #
 # Copyright (c) 2009 by Dreamwidth Studios, LLC.
 #
@@ -18,10 +19,12 @@ body<=
 <?_code
 {
     use strict;
-    use vars qw/ %GET %POST /;
+    use vars qw/ %GET %POST $title /;
 
     return BML::redirect( "$LJ::SITEROOT/" )
         unless LJ::is_enabled( 'payments' );
+
+    $title = $ML{'.title'};
 
     # this page uses new style JS
     LJ::need_res( 'stc/shop.css' );
@@ -29,22 +32,24 @@ body<=
 
     # get their shop/cart
     my $cart = DW::Shop->get->cart;
-    return "you don't have a shopping cart"
+    return $ML{'.error.nocart'}
         unless $cart;
-    return "your cart has nothing in it"
+    return $ML{'.error.emptycart'}
         unless $cart->has_items;
 
     # FIXME: if they have a $0 cart, we don't support that yet
-    return "sorry, we don't support \$0 carts yet"
+    return $ML{'.error.zerocart'}
         if $cart->total == 0.00;
 
     # establish the engine they're trying to use
     my $eng = DW::Shop::Engine->get( $GET{method}, $cart );
-    return 'payment method not supported'
+    return $ML{'.error.invalidpaymentmethod'}
         unless $eng;
 
-    # at this point, we have $eng, and we don't care what the
-    # actual payment backend is
+    # set the payment method on the cart
+    $cart->paymentmethod( $GET{method} );
+
+    # redirect to checkout url
     my $url = $eng->checkout_url;
     return $eng->errstr
         unless $url;
@@ -52,5 +57,5 @@ body<=
 }
 _code?>
 <=body
-title=>Checkout
+title=><?_code return $title; _code?>
 page?>
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/checkout.bml.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/checkout.bml.text	Sun Apr 26 19:47:15 2009 +0000
@@ -0,0 +1,11 @@
+;; -*- coding: utf-8 -*-
+
+.error.emptycart=Your cart is empty.
+
+.error.invalidpaymentmethod=The payment method you chose is not currently supported.
+
+.error.nocart=You do not have a shopping cart.
+
+.error.zerocart=Sorry, we don't support $0 carts yet.
+
+.title=Check Out
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/cmo_confirm.bml
--- a/htdocs/shop/cmo_confirm.bml	Sun Apr 26 19:12:29 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-<?_c
-
-#
-# shop/cmo_confirm.bml
-#
-# For users paying by check/money order to confirm.
-#
-# Authors:
-#      Mark Smith <mark@dreamwidth.org>
-#
-# Copyright (c) 2009 by Dreamwidth Studios, LLC.
-#
-# This program is free software; you may redistribute it and/or modify it under
-# the same terms as Perl itself.  For a copy of the license, please reference
-# 'perldoc perlartistic' or 'perldoc perlgpl'.
-#
-
-_c?><?page
-title=>Confirm Your Payment
-body<=
-<?_code
-{
-    use strict;
-    use vars qw(%GET %POST);
-
-    return BML::redirect( "$LJ::SITEROOT/" )
-        unless LJ::is_enabled( 'payments' );
-
-    my $cart = DW::Shop->get->cart
-        or return 'you do not seem to have a cart';
-    my $eng = DW::Shop::Engine::CheckMoneyOrder->new_from_cart( $cart );
-    return 'sorry, invalid cart'
-        unless $eng;
-
-    my $ordernum = $eng->cart->ordernum;
-
-    # cart must be in open state
-    return BML::redirect( "$LJ::SITEROOT/shop/cmo_receipt?ordernum=$ordernum" )
-        unless $eng->cart->state == $DW::Shop::STATE_OPEN;
-
-    # if they didn't post, give them a form
-    unless ( LJ::did_post() ) {
-        my $ret = '';
-        $ret .= LJ::Widget::ShopCart->render( receipt => 1, cart => $eng->cart );
-        $ret .= "<form method='post' action='$LJ::SITEROOT/shop/cmo_confirm?ordernum=$ordernum'>\n";
-        $ret .= LJ::form_auth();
-        $ret .= "<?p Please confirm that you wish to place the above order to $LJ::SITENAME in the amount of ";
-        $ret .= "\$". $eng->cart->display_total . ' USD.  Once you confirm this order, you will need to write a ';
-        $ret .= "check or money order and mail it to: some address, somewhere. p?>";
-        $ret .= '<div style="width: 350px;"><input style="float: left;" type="submit" value="Confirm Purchase" /> ';
-        $ret .= "<div style='float: right;'><a href='/shop/cmo_cancel?ordernum=$ordernum'>Cancel Purchase</a></div></div>";
-        $ret .= '</form>';
-        return $ret;
-    }
-
-    # okay, they posted, verify the auth code and
-    return 'invalid form auth'
-        unless LJ::check_form_auth();
-
-    # and now set the state, this has been checked out...
-    $eng->cart->state( $DW::Shop::STATE_CHECKOUT );
-
-    # they want to pay us, yippee!
-    my $rv = $eng->confirm_order;
-    return $eng->errstr
-        unless $rv;
-
-    # advise them the order has been placed and to watch their email
-    if ( $rv == 2 ) {
-        return '<?p Your order has been successfully placed.  Please remember to mail your check! p?><?p ' .
-               "<a href='$LJ::SITEROOT/shop/cmo_receipt?ordernum=$ordernum'>View Receipt</a> p?>";
-    }
-}
-_code?>
-<=body
-page?>
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/confirm.bml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/confirm.bml	Sun Apr 26 19:47:15 2009 +0000
@@ -0,0 +1,111 @@
+<?_c
+
+#
+# shop/confirm.bml
+#
+# The page used to confirm a user's order before we finally bill them.
+#
+# Authors:
+#      Mark Smith <mark@dreamwidth.org>
+#      Janine Costanzo <janine@netrophic.com>
+#
+# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself.  For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+
+_c?><?page
+body<=
+<?_code
+{
+    use strict;
+    use vars qw(%GET %POST $title);
+
+    return BML::redirect( "$LJ::SITEROOT/" )
+        unless LJ::is_enabled( 'payments' );
+
+    $title = $ML{'.title'};
+
+    my ( $ordernum, $token, $payerid ) = ( $GET{ordernum}, $GET{token}, $GET{PayerID} );
+    my ( $cart, $eng, $paymentmethod );
+
+    # use ordernum if we have it, otherwise use token/payerid
+    if ( $ordernum ) {
+        $cart = DW::Shop::Cart->get_from_ordernum( $ordernum );
+        return $ML{'.error.invalidordernum'}
+            unless $cart;
+
+        $paymentmethod = $cart->paymentmethod;
+        my $paymentmethod_class = 'DW::Shop::Engine::' . $DW::Shop::PAYMENTMETHODS{$paymentmethod}->{class};
+        $eng = $paymentmethod_class->new_from_cart( $cart );
+        return $ML{'.error.invalidcart'}
+            unless $eng;
+    } else {
+        return $ML{'.error.needtoken'}
+            unless $token;
+
+        # we can assume paypal is the engine if we have a token
+        $eng = DW::Shop::Engine::PayPal->new_from_token( $token );
+        return $ML{'.error.invalidtoken'}
+            unless $eng;
+
+        $cart = $eng->cart;
+        $ordernum = $cart->ordernum;
+        $paymentmethod = $cart->paymentmethod;
+    }
+
+    # cart must be in open state
+    return BML::redirect( "$LJ::SITEROOT/shop/receipt?ordernum=$ordernum" )
+        unless $cart->state == $DW::Shop::STATE_OPEN;
+
+    # if they didn't post, give them a form
+    unless ( LJ::did_post() ) {
+        # set the payerid for later
+        $eng->payerid( $payerid )
+            if $payerid;
+
+        my $ret = '';
+        $ret .= LJ::Widget::ShopCart->render( receipt => 1, cart => $cart );
+        $ret .= "<form method='post' action='$LJ::SITEROOT/shop/confirm?ordernum=$ordernum'>\n";
+        $ret .= LJ::form_auth();
+        $ret .= "<?p " . BML::ml( '.confirm', { sitename => $LJ::SITENAME, total => '<strong>$' . $cart->display_total . ' USD</strong>' } ) . " p?>";
+        $ret .= "<?p $ML{\".confirm.$paymentmethod\"} p?>";
+        $ret .= "<?p " . LJ::html_submit( confirm => $ML{'.btn.confirm'} );
+        $ret .= " <a href='$LJ::SITEROOT/shop/cancel?ordernum=$ordernum'>$ML{'.btn.cancel'}</a> p?>";
+        $ret .= '</form>';
+        return $ret;
+    }
+
+    # okay, they posted, verify the auth code
+    return $ML{'error.invalidauth'}
+        unless LJ::check_form_auth();
+
+    # and now set the state, this has been checked out...
+    $cart->state( $DW::Shop::STATE_CHECKOUT );
+
+    # they want to pay us, yippee!
+    my $rv = $eng->confirm_order;
+    return $eng->errstr
+        unless $rv;
+
+    my $ret;
+
+    # advise them the order has been placed and to watch their email
+    if ( $rv == 1 ) {
+        $ret .= "<?p $ML{\".success.$paymentmethod.immediate\"} p?>";
+    } elsif ( $rv == 2 ) {
+        my $address = "<?p $LJ::SITECOMPANY<br />Order #" . $cart->id . "<br />$LJ::SITEADDRESS p?>";
+        $ret .= "<?p " . BML::ml( ".success.$paymentmethod.processing", { address => $address } ) . " p?>";
+    }
+
+    $ret .= "<?p <a href='$LJ::SITEROOT/shop/receipt?ordernum=$ordernum'>$ML{'.btn.viewreceipt'}</a><br />";
+    $ret .= "<a href='$LJ::SITEROOT/shop'>$ML{'.btn.back'}</a> p?>";
+
+    return $ret;
+}
+_code?>
+<=body
+title=><?_code return $title; _code?>
+page?>
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/confirm.bml.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/confirm.bml.text	Sun Apr 26 19:47:15 2009 +0000
@@ -0,0 +1,31 @@
+;; -*- coding: utf-8 -*-
+
+.btn.back=Back to Shop
+
+.btn.cancel=Cancel Payment
+
+.btn.confirm=Confirm Payment
+
+.btn.viewreceipt=View Receipt
+
+.confirm=Please confirm your payment to [[sitename]] in the amount of [[total]].
+
+.confirm.checkmoneyorder=Once confirmed, you must mail a check or money order for the above amount to us.
+
+.confirm.paypal=Once confirmed, we will process your purchase as soon as PayPal notifies us of a successful transaction.
+
+.error.invalidcart=Your cart is invalid.
+
+.error.invalidordernum=Your order number is invalid.
+
+.error.invalidtoken=Your token is invalid.
+
+.error.needtoken=You did not provide an order number or token.
+
+.success.checkmoneyorder.processing=Your order has been successfully placed. It will be processed once we receive your check or money order. Please mail it to: [[address]] p?><?p <strong>Be sure to include the apartment and order numbers!</strong>
+
+.success.paypal.immediate=Your order has been successfully placed.  Within the next few minutes PayPal should finish processing and we will activate your order.  Watch your email!
+
+.success.paypal.processing=Your order has been successfully placed, but PayPal says it will take some time for your payment to finish processing.  As soon as it does, we will email you.
+
+.title=Confirm Your Payment
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/history.bml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/history.bml	Sun Apr 26 19:47:15 2009 +0000
@@ -0,0 +1,65 @@
+<?_c
+#
+# shop/history.bml
+#
+# Page that shows a user's past orders.
+#
+# Authors:
+#      Janine Costanzo <janine@netrophic.com>
+#
+# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself. For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+_c?><?page
+body<=
+<?_code
+{
+    use strict;
+    use vars qw/ %GET %POST $title /;
+
+    my $remote = LJ::get_remote()
+        or return "<?needlogin?>";
+
+    $title = $ML{'.title'};
+
+    # this page uses new style JS
+    LJ::need_res( 'stc/shop.css' );
+    LJ::set_active_resource_group( 'jquery' );
+
+    my @carts = DW::Shop::Cart->get_all( $remote, finished => 1 );
+
+    unless ( @carts ) {
+        return BML::ml( '.nocarts.extra', { aopts => "href='$LJ::SITEROOT/shop'" } )
+            if LJ::is_enabled( 'payments' );
+        return $ML{'.nocarts'};
+    }
+
+    my $ret;
+    $ret .= "<table class='shop-cart'>";
+    $ret .= "<tr><th>$ML{'.cart.header.ordernumber'}</th><th>$ML{'.cart.header.date'}</th><th>$ML{'.cart.header.total'}</th>";
+    $ret .= "<th>$ML{'.cart.header.paymentmethod'}</th><th>$ML{'.cart.header.status'}</th><th>$ML{'.cart.header.details'}</th></tr>";
+    foreach my $cart ( @carts ) {
+        my $state = $cart->state;
+        my $paymentmethod = $cart->paymentmethod;
+        my $date = DateTime->from_epoch( epoch => $cart->starttime );
+
+        $ret .= "<tr>";
+        $ret .= "<td>" . $cart->id . "</td>";
+        $ret .= "<td>" . $date->strftime( "%F %r %Z" ) . "</td>";
+        $ret .= "<td>\$" . $cart->total . " USD</td>";
+        $ret .= "<td>$ML{\"/shop/receipt.bml.cart.paymentmethod.$paymentmethod\"}</td>";
+        $ret .= "<td>$ML{\"/shop/receipt.bml.cart.status.$state\"}</td>";
+        $ret .= "<td><a href='$LJ::SITEROOT/shop/receipt?ordernum=" . $cart->ordernum . "'>$ML{'.cart.details'}</a></td>";
+        $ret .= "</tr>";
+    }
+    $ret .= "</table>";
+
+    return $ret;
+}
+_code?>
+<=body
+title=><?_code return $title; _code?>
+page?>
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/history.bml.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/history.bml.text	Sun Apr 26 19:47:15 2009 +0000
@@ -0,0 +1,21 @@
+;; -*- coding: utf-8 -*-
+
+.cart.details=View
+
+.cart.header.date=Date
+
+.cart.header.details=Details
+
+.cart.header.ordernumber=Order Number
+
+.cart.header.paymentmethod=Payment Method
+
+.cart.header.status=Status
+
+.cart.header.total=Total
+
+.nocarts=You have not placed any orders.
+
+.nocarts.extra=You have not placed any orders.  <a [[aopts]]>Want to purchase something?</a>
+
+.title=Order History
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/pp_confirm.bml
--- a/htdocs/shop/pp_confirm.bml	Sun Apr 26 19:12:29 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-<?_c
-
-#
-# shop/pp_confirm.bml
-#
-# The return page when we get back from PayPal.  Used to confirm a user's order
-# before we finally bill them.
-#
-# Authors:
-#      Mark Smith <mark@dreamwidth.org>
-#
-# Copyright (c) 2009 by Dreamwidth Studios, LLC.
-#
-# This program is free software; you may redistribute it and/or modify it under
-# the same terms as Perl itself.  For a copy of the license, please reference
-# 'perldoc perlartistic' or 'perldoc perlgpl'.
-#
-
-_c?><?page
-title=>Confirm Your Payment
-body<=
-<?_code
-{
-    use strict;
-    use vars qw(%GET %POST);
-
-    return BML::redirect( "$LJ::SITEROOT/" )
-        unless LJ::is_enabled( 'payments' );
-
-    my ( $token, $payerid ) = ( $GET{token}, $GET{PayerID} );
-    return 'sorry, need token at least'
-        unless $token;
-
-    my $eng = DW::Shop::Engine::PayPal->new_from_token( $GET{token} );
-    return 'sorry, invalid token'
-        unless $eng;
-
-    # cart must be in open state
-    return BML::redirect( "$LJ::SITEROOT/shop/pp_receipt?token=" . $eng->token )
-        unless $eng->cart->state == $DW::Shop::STATE_OPEN;
-
-    # if they didn't post, give them a form
-    unless ( LJ::did_post() ) {
-        # set the payerid for later
-        $eng->payerid( $payerid )
-            if $payerid;
-
-        my $ret = '';
-        $ret .= LJ::Widget::ShopCart->render( receipt => 1, cart => $eng->cart );
-        $ret .= "<form method='post' action='$LJ::SITEROOT/shop/pp_confirm?token=$token'>\n";
-        $ret .= LJ::form_auth();
-        $ret .= "<?p Please confirm your payment to $LJ::SITENAME in the amount of \$". $eng->cart->display_total . ' USD. ';
-        $ret .= 'Once confirmed, we will process your purchase as soon as PayPal notifies us of a successful transaction. p?>';
-        $ret .= '<div style="width: 350px;"><input style="float: left;" type="submit" value="Confirm Purchase" /> ';
-        $ret .= "<div style='float: right;'><a href='/shop/pp_cancel?token=$token'>Cancel Purchase</a></div></div>";
-        $ret .= '</form>';
-        return $ret;
-    }
-
-    # okay, they posted, verify the auth code and
-    return 'invalid form auth'
-        unless LJ::check_form_auth();
-
-    # and now set the state, this has been checked out...
-    $eng->cart->state( $DW::Shop::STATE_CHECKOUT );
-
-    # they want to pay us, yippee!
-    my $rv = $eng->confirm_order;
-    return $eng->errstr
-        unless $rv;
-
-    # advise them the order has been placed and to watch their email
-    if ( $rv == 1 ) {
-        return '<?p Your order has been successfully placed.  Within the next few minutes PayPal should ' .
-               'finish processing and we will activate your order.  Watch your email! p?><?p ' .
-               "<a href='$LJ::SITEROOT/shop/pp_receipt?token=$token'>View Receipt</a> p?>";
-
-    } elsif ( $rv == 2 ) {
-        return '<?p Your order has been successfully placed, but PayPal says it will take some time for ' .
-               'your payment to finish processing.  As soon as it does, we will email you. p?><?p ' .
-               "<a href='$LJ::SITEROOT/shop/pp_receipt?token=$token'>View Receipt</a> p?>";
-    }
-}
-_code?>
-<=body
-page?>
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/receipt.bml
--- a/htdocs/shop/receipt.bml	Sun Apr 26 19:12:29 2009 +0000
+++ b/htdocs/shop/receipt.bml	Sun Apr 26 19:47:15 2009 +0000
@@ -3,11 +3,10 @@
 #
 # shop/receipt.bml
 #
-# Serves up a simple receipt for the user to view the status of the order and
-# print out and save if they want.
+# This page shows your receipt for your order.
 #
 # Authors:
-#      Mark Smith <mark@dreamwidth.org>
+#      Janine Costanzo <janine@netrophic.com>
 #
 # Copyright (c) 2009 by Dreamwidth Studios, LLC.
 #
@@ -16,53 +15,61 @@
 # 'perldoc perlartistic' or 'perldoc perlgpl'.
 #
 
-_c?><?page
-title=>Paid Account Receipt
-body<=
+_c?>
 <?_code
 {
     use strict;
-    use vars qw(%GET);
 
     return BML::redirect( "$LJ::SITEROOT/" )
         unless LJ::is_enabled( 'payments' );
 
-    my $payment_id = $GET{'payment_id'};
-    my $email = $GET{'email'};
+    my $ordernum = $GET{ordernum};
 
-    my $payment = DW::Pay::get_payment_details($payment_id);
+    my $cart = DW::Shop::Cart->get_from_ordernum( $ordernum );
+    return $ML{'.error.invalidordernum'}
+        unless $cart;
 
-    if ($payment) {
-        my $order = DW::Pay::pp_get_order_details($payment_id);
-        if ($order->{'email'} eq $GET{'email'}) {
+    my $state = $cart->state;
+    my $paymentmethod = $cart->paymentmethod;
 
-            ### FORMAT RECEIPT HERE
-            my $ret = "<p>This is your receipt for $LJ::SITENAME paid services. Please bookmark this URL for your own records.</p><table style='width: 450px'>";
-            my %status = (
-                          'pending'        => "Pending, <a href='$LJ::SITEROOT/shop/pp_confirm?token=$order->{token}'>awaiting confirmation</a>",
-                          'processing'     => 'Processing',
-                          'paid-pending'   => 'Payment is complete, thanks for your support!',
-                          'paid-completed' => 'Payment is complete, thanks for your support!', 
-                          );
-            $ret .= '<tr><th style="text-align: left">Status:</th>';
-            $ret .= '<td style="text-align: right">'.$status{$payment->{'status'}}.'</td></tr>';
-            $ret .= '<tr><th style="text-align: left">Account Type:</th>';
-            $ret .= '<td style="text-align: right">'.DW::Pay::type_name($payment->{'typeid'}).'</td></tr>';
-            $ret .= '<tr><th style="text-align: left">Duration:</th>';
-            $ret .= '<td style="text-align: right">'.($payment->{'duration'} == 99 ? 'Permanent' : $payment->{'duration'}.' months').'</td></tr>';
-            $ret .= '<tr><th style="text-align: left">Total:</th><td style="text-align: right">$'.$payment->{'amount'}.' USD</td></tr>';
-            $ret .= '</table>';
+    # cart cannot be in open, closed, or checkout state
+    return BML::redirect( "$LJ::SITEROOT/shop/cart" )
+        if $state == $DW::Shop::STATE_OPEN || $state == $DW::Shop::STATE_CLOSED || $state == $DW::Shop::STATE_CHECKOUT;
 
-            $ret .='<?p A receipt for your purchase has been emailed to you. You may log into your account at <a href="http://www.paypal.com/">www.paypal.com</a> to view details of this transaction. p?>';
+    my $title = BML::ml( '.title', { num => $cart->id } );
 
-            return $ret;
-        } else {
-            return "You are not verified to view this receipt";
+    my $ret;
+    $ret .= qq{
+        <html>
+        <head>
+        <style type='text/css'>
+        table {
+            border: 1px solid #000;
+            border-spacing: 0;
         }
-    } else {
-        return "That order could not be found.";
+        table td, table th {
+            border: 1px solid #000;
+            padding: 5px;
+        }
+        table td.total {
+            font-weight: bold;
+            text-align: right;
+        }
+        </style>
+        <title>$title</title>
+        </head>
+        <body>
+    };
+    $ret .= "<h1>$title</h1>";
+    $ret .= "<p>" . BML::ml( '.cart.status', { status => $ML{".cart.status.$state"} } ) . "<br />";
+    $ret .= BML::ml( '.cart.paymentmethod', { paymentmethod => $ML{".cart.paymentmethod.$paymentmethod"} } ) . "</p>";
+    if ( $paymentmethod eq 'checkmoneyorder' ) {
+        my $address = "<p>$LJ::SITECOMPANY<br />Order #" . $cart->id . "<br />$LJ::SITEADDRESS</p>";
+        $ret .= "<p>" . BML::ml( ".cart.paymentmethod.$paymentmethod.extra", { address => $address } ) . "</p>";
     }
+    $ret .= LJ::Widget::ShopCart->render( receipt => 1, cart => $cart );
+    $ret .= "</body></html>";
+
+    return $ret;
 }
 _code?>
-<=body
-page?>
diff -r 3f7acfee7d4d -r 9cc8ea02116c htdocs/shop/receipt.bml.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/shop/receipt.bml.text	Sun Apr 26 19:47:15 2009 +0000
@@ -0,0 +1,34 @@
+;; -*- coding: utf-8 -*-
+
+.cart.status=<strong>Current Order Status:</strong> [[status]]
+
+.cart.status.1|note=This status should never be possible on the receipt page!
+.cart.status.1=Open
+
+.cart.status.2|note=This status should never be possible on the receipt page!
+.cart.status.2=Checked out
+
+.cart.status.3=Waiting for payment
+
+.cart.status.4=Payment received
+
+.cart.status.5=Processed and completed
+
+.cart.status.6=Refund approved and pending
+
+.cart.status.7=Refunded
+
+.cart.status.8|note=This status should never be possible on the receipt page!
+.cart.status.8=Closed before completion
+
+.cart.paymentmethod=<strong>Payment Method:</strong> [[paymentmethod]]
+
+.cart.paymentmethod.checkmoneyorder=Check/Money Order
+
+.cart.paymentmethod.checkmoneyorder.extra=Please mail your check or money order to: [[address]]
+
+.cart.paymentmethod.paypal=PayPal
+
+.error.invalidordernum=Your order number is invalid.
+
+.title=Order #[[num]]
--------------------------------------------------------------------------------