[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
janinedog.
Files modified:
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]](https://www.dreamwidth.org/img/silk/identity/user.png)
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]] --------------------------------------------------------------------------------