377 lines
12 KiB
Perl
377 lines
12 KiB
Perl
#!/usr/bin/perl
|
|
#
|
|
# DW::Controller::Shop
|
|
#
|
|
# This controller is for shop handlers.
|
|
#
|
|
# Authors:
|
|
# Mark Smith <mark@dreamwidth.org>
|
|
#
|
|
# Copyright (c) 2010-2018 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 DW::Controller::Shop;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Carp qw/ croak confess /;
|
|
|
|
use DW::Controller;
|
|
use DW::Pay;
|
|
use DW::Routing;
|
|
use DW::Shop;
|
|
use DW::Template;
|
|
use LJ::JSON;
|
|
|
|
# routing directions
|
|
DW::Routing->register_string( '/shop', \&shop_index_handler, app => 1 );
|
|
DW::Routing->register_string( '/shop/receipt', \&shop_receipt_handler, app => 1 );
|
|
DW::Routing->register_string( '/shop/checkout', \&shop_checkout_handler, app => 1 );
|
|
DW::Routing->register_string( '/shop/history', \&shop_history_handler, app => 1 );
|
|
DW::Routing->register_string( '/shop/cancel', \&shop_cancel_handler, app => 1 );
|
|
DW::Routing->register_string( '/shop/cart', \&shop_cart_handler, app => 1 );
|
|
DW::Routing->register_string( '/shop/confirm', \&shop_confirm_handler, app => 1 );
|
|
|
|
# our basic shop controller, this does setup that is unique to all shop
|
|
# pages and everybody should call this first. returns the same tuple as
|
|
# the controller method.
|
|
sub _shop_controller {
|
|
my %args = (@_);
|
|
my $r = DW::Request->get;
|
|
|
|
# if payments are disabled, do nothing
|
|
unless ( LJ::is_enabled('payments') ) {
|
|
return ( 0, error_ml('shop.unavailable') );
|
|
}
|
|
|
|
# if they're banned ...
|
|
if ( my $err = DW::Shop->remote_sysban_check ) {
|
|
return ( 0, DW::Template->render_template( 'error.tt', { message => $err } ) );
|
|
}
|
|
|
|
# if they aren't on the shop domain, redirect
|
|
if ( $LJ::DOMAIN_SHOP && $r->host ne $LJ::DOMAIN_SHOP ) {
|
|
return ( 0, $r->redirect("$LJ::SHOPROOT/") );
|
|
}
|
|
|
|
# basic controller setup
|
|
my ( $ok, $rv ) = controller(%args);
|
|
return ( $ok, $rv ) unless $ok;
|
|
|
|
# the entire shop uses these files
|
|
LJ::need_res('stc/shop.css');
|
|
LJ::set_active_resource_group('foundation');
|
|
|
|
# figure out what shop/cart to use
|
|
$rv->{shop} = DW::Shop->get;
|
|
$rv->{cart} =
|
|
$r->get_args->{newcart} ? DW::Shop::Cart->new_cart( $rv->{u} ) : $rv->{shop}->cart;
|
|
$rv->{cart} =
|
|
$r->get_args->{ordernum}
|
|
? DW::Shop::Cart->get_from_ordernum( $r->get_args->{ordernum} )
|
|
: $rv->{shop}->cart;
|
|
|
|
# populate vars with cart display template
|
|
$rv->{cart_display} = DW::Template->template_string( 'shop/cartdisplay.tt', $rv );
|
|
|
|
# call any hooks to do things before we return success
|
|
LJ::Hooks::run_hooks( 'shop_controller', $rv );
|
|
|
|
return ( 1, $rv );
|
|
}
|
|
|
|
# handles the shop index page
|
|
sub shop_index_handler {
|
|
my ( $ok, $rv ) = _shop_controller( anonymous => 1 );
|
|
return $rv unless $ok;
|
|
|
|
$rv->{shop_config} = \%LJ::SHOP;
|
|
|
|
return DW::Template->render_template( 'shop/index.tt', $rv );
|
|
}
|
|
|
|
# view the receipt for a specific order
|
|
sub shop_receipt_handler {
|
|
|
|
# this doesn't do form handling or state changes, don't need full shop_controller
|
|
my $r = DW::Request->get;
|
|
return $r->redirect("$LJ::SITEROOT/") unless LJ::is_enabled('payments');
|
|
|
|
if ( my $err = DW::Shop->remote_sysban_check ) {
|
|
return DW::Template->render_template( 'error.tt', { message => $err } );
|
|
}
|
|
|
|
my ( $ok, $rv ) = controller( anonymous => 1 );
|
|
return $rv unless $ok;
|
|
|
|
my $args = $r->get_args;
|
|
my $scope = '/shop/receipt.tt';
|
|
|
|
# we don't have to be logged in, but we do need an ordernum passed in
|
|
my $ordernum = $args->{ordernum} // '';
|
|
|
|
my $cart = DW::Shop::Cart->get_from_ordernum($ordernum);
|
|
return error_ml("$scope.error.invalidordernum") unless $cart;
|
|
|
|
# cart cannot be in open, closed, or checkout state
|
|
my %invalid_state = (
|
|
$DW::Shop::STATE_OPEN => 1,
|
|
$DW::Shop::STATE_CLOSED => 1,
|
|
$DW::Shop::STATE_CHECKOUT => 1,
|
|
);
|
|
return $r->redirect("$LJ::SHOPROOT/cart") if $invalid_state{ $cart->state };
|
|
|
|
# set up variables for template
|
|
my $vars = { cart => $cart };
|
|
|
|
$vars->{orderdate} = DateTime->from_epoch( epoch => $cart->starttime );
|
|
$vars->{carttable} = LJ::Widget::ShopCart->render( receipt => 1, cart => $cart );
|
|
|
|
return DW::Template->render_template( 'shop/receipt.tt', $vars );
|
|
}
|
|
|
|
# handles the shop checkout page
|
|
sub shop_checkout_handler {
|
|
my ( $ok, $rv ) = _shop_controller( anonymous => 1 );
|
|
return $rv unless $ok;
|
|
|
|
my $cart = $rv->{cart};
|
|
my $r = DW::Request->get;
|
|
my $GET = $r->get_args;
|
|
my $scope = 'shop/checkout.tt';
|
|
|
|
return error_ml("$scope.error.nocart") unless $cart;
|
|
return error_ml("$scope.error.emptycart") unless $cart->has_items;
|
|
|
|
# FIXME: if they have a $0 cart, we don't support that yet
|
|
return error_ml("$scope.error.zerocart")
|
|
if $cart->total_cash == 0.00 && $cart->total_points == 0;
|
|
|
|
# establish the engine they're trying to use
|
|
my $eng = DW::Shop::Engine->get( $GET->{method}, $cart );
|
|
return error_ml("$scope.error.invalidpaymentmethod")
|
|
unless $eng;
|
|
|
|
# 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;
|
|
return $r->redirect($url);
|
|
|
|
}
|
|
|
|
sub shop_history_handler {
|
|
my ( $ok, $rv ) = _shop_controller();
|
|
return $rv unless $ok;
|
|
|
|
my $cart = $rv->{cart};
|
|
my $r = DW::Request->get;
|
|
my $remote = $rv->{remote};
|
|
|
|
my @carts = DW::Shop::Cart->get_all( $remote, finished => 1 );
|
|
foreach my $cart (@carts) {
|
|
$cart->{date} = DateTime->from_epoch( epoch => $cart->starttime );
|
|
}
|
|
|
|
return DW::Template->render_template( 'shop/history.tt', { carts => \@carts } );
|
|
}
|
|
|
|
# handles the shop cancel page
|
|
sub shop_cancel_handler {
|
|
my ( $ok, $rv ) = _shop_controller( anonymous => 1 );
|
|
return $rv unless $ok;
|
|
|
|
my $r = DW::Request->get;
|
|
my $GET = $r->get_args;
|
|
my $scope = 'shop/cancel.tt';
|
|
|
|
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 error_ml("$scope.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 error_ml("$scope.error.invalidcart")
|
|
unless $eng;
|
|
}
|
|
else {
|
|
return error_ml("$scope'.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 error_ml("$scope'.error.invalidtoken")
|
|
unless $eng;
|
|
|
|
$cart = $eng->cart;
|
|
$ordernum = $cart->ordernum;
|
|
}
|
|
|
|
# cart must be in open state
|
|
return $r->redirect("$LJ::SHOPROOT/receipt?ordernum=$ordernum")
|
|
unless $cart->state == $DW::Shop::STATE_OPEN;
|
|
|
|
# cancel payment and discard cart
|
|
if ( $eng->cancel_order ) {
|
|
return $r->redirect("$LJ::SHOPROOT?newcart=1");
|
|
}
|
|
|
|
return error_ml("$scope.error.cantcancel");
|
|
|
|
}
|
|
|
|
# Allows for viewing and manipulating the shopping cart.
|
|
sub shop_cart_handler {
|
|
my ( $ok, $rv ) = _shop_controller( anonymous => 1 );
|
|
return $rv unless $ok;
|
|
|
|
my $cart = $rv->{cart};
|
|
my $r = DW::Request->get;
|
|
my $remote = $rv->{remote};
|
|
my $GET = $r->get_args;
|
|
my $POST = $r->post_args;
|
|
|
|
if ( $r->did_post() ) {
|
|
|
|
# checkout methods depend on which button was clicked
|
|
my $cm;
|
|
$cm = 'checkmoneyorder' if $POST->{checkout_cmo} || $POST->{checkout_free};
|
|
$cm = 'stripe' if $POST->{checkout_stripe};
|
|
|
|
# check out?
|
|
return $r->redirect("$LJ::SHOPROOT/checkout?method=$cm")
|
|
if defined $cm;
|
|
|
|
# remove selected items
|
|
if ( $POST->{'removeselected'} ) {
|
|
return error_ml('widget.shopcart.error.nocart') unless $cart;
|
|
|
|
foreach my $val ( keys %$POST ) {
|
|
next unless $POST->{$val} && $val =~ /^remove_(\d+)$/;
|
|
$cart->remove_item($1);
|
|
}
|
|
}
|
|
|
|
# discard entire cart
|
|
if ( $POST->{'discard'} ) {
|
|
return $r->redirect("$LJ::SHOPROOT?newcart=1");
|
|
}
|
|
|
|
}
|
|
|
|
my $vars = {
|
|
duplicate => $GET->{duplicate},
|
|
failed => $GET->{failed},
|
|
cart_widget => LJ::Widget::ShopCart->render
|
|
};
|
|
|
|
return DW::Template->render_template( 'shop/cart.tt', $vars );
|
|
}
|
|
|
|
# The page used to confirm a user's order before we finally bill them.
|
|
sub shop_confirm_handler {
|
|
my ( $ok, $rv ) = _shop_controller( anonymous => 1 );
|
|
return $rv unless $ok;
|
|
|
|
my $r = DW::Request->get;
|
|
my $remote = $rv->{remote};
|
|
my $GET = $r->get_args;
|
|
my $POST = $r->post_args;
|
|
my $vars;
|
|
|
|
my $scope = "/shop/confirm.tt";
|
|
|
|
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 error_ml("$scope.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 error_ml("$scope.error.invalidcart")
|
|
unless $eng;
|
|
}
|
|
else {
|
|
return error_ml("$scope.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 error_ml("$scope.error.invalidtoken")
|
|
unless $eng;
|
|
|
|
$cart = $eng->cart;
|
|
$ordernum = $cart->ordernum;
|
|
$paymentmethod = $cart->paymentmethod;
|
|
}
|
|
|
|
# cart must be in open/checkout state
|
|
return $r->redirect("$LJ::SHOPROOT/receipt?ordernum=$ordernum")
|
|
unless $cart->state == $DW::Shop::STATE_OPEN || $cart->state == $DW::Shop::STATE_CHECKOUT;
|
|
|
|
# check email early so we can re-render the form on error
|
|
my ( $email_checkbox, @email_errors );
|
|
if ( $r->did_post && !$cart->userid ) {
|
|
LJ::check_email( $POST->{email}, \@email_errors, $POST, \$email_checkbox );
|
|
}
|
|
|
|
if ( $r->did_post && !@email_errors ) {
|
|
if ( $cart->userid ) {
|
|
my $u = LJ::load_userid( $cart->userid );
|
|
$cart->email( $u->email_raw );
|
|
}
|
|
else {
|
|
# email checked above
|
|
$cart->email( $POST->{email} );
|
|
}
|
|
|
|
# and now set the state, we're waiting for the user to send us money
|
|
$cart->state($DW::Shop::STATE_CHECKOUT);
|
|
|
|
# they want to pay us, yippee!
|
|
my $confirm = $eng->confirm_order;
|
|
return $eng->errstr
|
|
unless $confirm;
|
|
$vars->{confirm} = $confirm;
|
|
|
|
}
|
|
|
|
if ( !$r->did_post() || @email_errors ) {
|
|
|
|
# set the payerid for later
|
|
$eng->payerid($payerid)
|
|
if $payerid;
|
|
}
|
|
|
|
$vars->{showform} = ( !$r->did_post || @email_errors );
|
|
$vars->{email_errors} = \@email_errors;
|
|
$vars->{cart} = $cart;
|
|
$vars->{ordernum} = $ordernum;
|
|
$vars->{email} = $POST->{email};
|
|
$vars->{widget} = LJ::Widget::ShopCart->render( confirm => 1, cart => $cart );
|
|
$vars->{paymentmethod} = $paymentmethod;
|
|
|
|
return DW::Template->render_template( 'shop/confirm.tt', $vars );
|
|
}
|
|
|
|
1;
|