Rapid Web Application Deployment with Maypole : Part 2
by Simon Cozens
|
Pages: 1, 2
Check Out
The hardest part about building an e-commerce application is interacting with the payment and credit-card fulfillment service. We'll use the Business::OnlinePayment module to handle that side of things, and handle the order fulfillment by simply sending an email.
The actual check-out page needs to collect credit card and delivery information, and so it doesn't actually need any objects; the only object we actually need is the ISellIt::User, and that was stashed away in the request object by the authentication routine. However, we do want to display the total cost. So to make things easier we'll add an action and compute this in Perl. We make the total cost a method on the user, so we can use this later:
package ISellIt::User;
use List::Util qw(sum);
sub basket_cost {
my $self = shift;
sum map { $_->item->price }
$self->basket_items
}
And define checkout to add this total to our template:
sub checkout :Exported {
my ($self, $r) = @_;
$r->{template_args}{total_cost} = $r->{user}->basket_cost;
}
Now we write our user/checkout template:
[% PROCESS header %]
<h2> Check out </h2>
<p> Please enter your credit card and delivery details. </p>
<form method="post" action="https://www.isellit.com/user/do_checkout">
<P>
First name: <input name="first_name" value="[% my.first_name %]"><BR>
Last name: <input name="last_name" value="[% my.last_name %]"></P>
<P>
Street address: <input name="address" value="[% my.address1 %]"><BR>
City: <input name="city" value="[% my.address2 %]"><BR>
State: <input name="state" value="[% my.state %]">
Zip: <input name="zip" value="[% my.postal_code %]">
</P>
<P>
Card type: <select name="type">
<option>Visa</option>
<option>Mastercard</option>
...
</select>
Card number: <input name="card_number">
Expiration: <input name="expiration"> <BR>
Total: $ [% total_price %]
</P>
<P>
Please click <B>once</B> and wait for the payment to be
authorised.... <input type="submit" value="order">
</form>
What happens when this data is sent to the do_checkout action? (Over SSL, you'll notice.) First of all, we'll check if the user has entered address details for the first time, and if so, store them in the database. Perhaps unnecessary in this day of browsers that auto-fill forms, but it's still a convenience. Maypole stores the POST'ed in parameters in params:
sub do_checkout :Exported {
my ($self, $r) = @_;
my %params = %{$r->{params}};
my $user = $r->{user};
$user->address1($params{address}) unless $user->address1;
$user->address2($params{city}) unless $user->address2;
$user->state($params{state}) unless $user->state;
$user->postal_code($params{zip}) unless $user->postal_code;
We need to construct a request to go out via Business::OnlinePayment; thankfully, the form parameters we've received are going to be precisely in the format that OnlinePayment wants, thanks to careful form design. All we need to do is to insert our account details and the total:
my $tx = new Business::OnlinePayment("TCLink");
$tx->content(%params,
type => "cc",
login => VENDOR_LOGIN,
password => VENDOR_PASSWORD,
action => 'Normal Authorization'
amount => $r->{user}->basket_total
);
Now we can submit the payment and see what happens. If there's a problem, we add a message to the template and send the user back again:
$tx->submit;
if (!$tx->is_success) {
$r->{template_args}{message} =
"There was a problem authorizing your transaction: ".
$tx->error_message;
$r->{template} = "checkout";
return;
}
Otherwise, we have our money; we probably want to tell the box-shifters about it, or we lose customers fast:
fulfill_order(
address_details => $r->{params},
order_details => [ map { $_->item } $r->{user}->cart_items ],
cc_auth => $tx->authorization
);
And now we empty the shopping cart, and send the user on his way:
$_->delete for $r->{user}->cart_items;
$r->{template} = "frontpage";
}
Done! We've taken a user from logging in, adding goods to the cart, credit card validation, and checkout. But... wait. How did we get our user in the first place?
Registering a User
We have to find a way to sign a user up. This is actually not that hard, particularly since we can use the example of Flox in the Maypole manual. First, we'll add a "register" link to our login template:
<P>New user? <A HREF="/user/register">Sign up!</A></P>
This page doesn't require any objects to be loaded up, since it's just going to display a registration form; we can just add our template in /user/register:
[% INCLUDE header %]
<P>Welcome to buying with iSellIt!</P>
<P>To set up your account, we only need a few details from you:
</P>
<FORM METHOD="POST" ACTION="/user/do_register">
<P>Your name:
<input name="first_name">
<input name="last_name"> </P>
<P>Your email address: <input name="email"> </P>
<P>Please choose a password: <input name="password"> </P>
<input type="submit" name="Register" value="Register">
</FORM>
As before, we need to explain to Class::DBI::FromCGI how these fields are to be edited:
ISellIt::User->untaint_columns(
printable => [qw/first_name last_name password/],
email => [qw/email/],
);
And now we can write our do_register event, using the FromCGI style:
sub do_register :Exported {
my ($self, $r) = @_;
my $h = CGI::Untaint->new(%{$r->{params}});
my $user = $self->create_from_cgi($h);
If there were any problems, we send them back to the register form again:
if (my %errors = $obj->cgi_update_errors) {
$r->{template_args}{cgi_params} = $r->{params};
$r->{template_args}{errors} = \%errors;
$r->{template} = "register";
return;
}
Otherwise, we now have a user; we need to issue the cookie as if the user had logged in normally. Again, this is something that UserSessionCookie looks after for us:
$r->{user} = $user;
$r->login_user($user->id);
And finally we send the user on his or her way again:
$r->{template} = "frontpage";
}
There we go: now we can create new users; provision of a password reminder function is an exercise for the interested reader.
Maypole Summary
We've done it -- we've created an e-commerce store in a very short space of time and with a minimal amount of code. One of the things that I like about Maypole is the extent to which you only need to code your business logic; all of the display templates can be mocked up and then shipped off to professionals, and the rest of the work is just handled magically behind the scenes by Maypole.
Thanks to the TPF funding of Maypole, we now have an extensive user manual with several case studies (this one included), and a lively user and developer community. I hope you too will be joining it soon!

