#!/usr/bin/perl # # DW::Controller::Admin::Pay # # Manage payment history and status. Requires 'payments' privs. # # Authors: # Jen Griffin # # Copyright (c) 2020 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::Admin::Pay; use strict; use DW::Controller; use DW::Controller::Admin; use DW::Routing; use DW::Template; use DW::Pay; use DW::Shop; use DW::Shop::Cart; use DW::Shop::Engine; use DW::InviteCodes; use DateTime; use Storable qw/ thaw /; DW::Controller::Admin->register_admin_page( '/', path => 'pay', ml_scope => '/admin/pay/index.tt', privs => ['payments'] ); DW::Routing->register_string( "/admin/pay/index", \&main_controller, app => 1 ); DW::Routing->register_string( "/admin/pay/view", \&view_controller, app => 1 ); DW::Routing->register_string( "/admin/pay/striptime", \&striptime_controller, app => 1 ); sub main_controller { my ( $ok, $rv ) = controller( form_auth => 1, privcheck => ['payments'] ); return $rv unless $ok; my $r = DW::Request->get; my $scope = '/admin/pay/viewuser.tt'; my $vars = {}; return DW::Template->render_template( 'admin/pay/index.tt', $vars ) unless $r->did_post or $r->get_args->{view}; if ( $r->did_post ) { my $remote = $rv->{remote}; my $post = $r->post_args; return error_ml("$scope.error.formcheck") unless $post->{givetime}; my $who = $post->{user}; my $u = LJ::load_user($who); return error_ml( "$scope.error.invalidaccount", { user => LJ::ehtml($who) } ) unless $u; if ( $post->{submit} eq 'Edit' ) { my $datetime = $post->{datetime}; return error_ml("$scope.error.invalidtime") unless $datetime =~ /^\d{4}\-\d\d\-\d\d *( \d\d(:\d\d){1,2})?$/; my $done = DW::Pay::edit_expiration_datetime( $u, $datetime ); return error_ml( "$scope.error.sys", { err => DW::Pay::error_text() } ) unless $done; LJ::statushistory_add( $u, $remote, 'paidstatus', "Admin override: edit expiration date/time: $datetime" ); return $r->redirect("$LJ::SITEROOT/admin/pay/index?view=$u->{user}"); } my $type = $post->{type}; return error_ml("$scope.error.invalidstatus") unless $type =~ /^(?:seed|premium|paid|expire)$/; my $months = $post->{months} || 0; my $days = $post->{days} || 0; $months = 99 if $type eq 'seed'; if ( $type eq 'expire' ) { my $done = DW::Pay::expire_user( $u, force => 1 ); return error_ml( "$scope.error.sys", { err => DW::Pay::error_text() } ) unless $done; LJ::statushistory_add( $u, $remote, 'paidstatus', "Admin override: expired account." ); } else { my $done = DW::Pay::add_paid_time( $u, $type, $months, $days ); return error_ml( "$scope.error.sys", { err => DW::Pay::error_text() } ) unless $done; LJ::statushistory_add( $u, $remote, 'paidstatus', "Admin override: gave paid time to user: months=$months days=$days type=$type" ); if ( $post->{sendemail} ) { LJ::send_mail( { to => $u->email_raw, from => $LJ::ACCOUNTS_EMAIL, fromname => $LJ::SITENAME, subject => LJ::Lang::ml( 'shop.email.admin.subject', { sitename => $LJ::SITENAME } ), body => LJ::Lang::ml( 'shop.email.admin.body', { touser => $u->display_name, type => $type, nummonths => $months, numdays => $days, sitename => $LJ::SITENAME, } ), } ); } } return $r->redirect("$LJ::SITEROOT/admin/pay/index?view=$u->{user}"); } # end if did_post my $username = $r->get_args->{view}; return error_ml( "$scope.error.invalidaccount", { user => LJ::ehtml($username) } ) unless $vars->{u} = LJ::load_user($username); $vars->{ps} = DW::Pay::get_paid_status( $vars->{u} ); $vars->{carts} = [ DW::Shop::Cart->get_all( $vars->{u} ) ]; $vars->{type_name} = sub { DW::Pay::type_name( $_[0]->{typeid} ) }; $vars->{from_epoch} = sub { DateTime->from_epoch( epoch => $_[0] ) }; $vars->{mysql_time} = sub { $_[0] ? LJ::mysql_time( $_[0] ) : "" }; $vars->{ago_text} = sub { my $exp = LJ::ago_text( $_[0] ); $exp =~ s/ ago//; return $exp; }; $vars->{is_pending} = sub { $_[0] == $DW::Shop::STATE_PEND_PAID ? 1 : 0 }; return DW::Template->render_template( 'admin/pay/viewuser.tt', $vars ); } sub striptime_controller { my ( $ok, $rv ) = controller( form_auth => 1, privcheck => ['payments'] ); return $rv unless $ok; my $r = DW::Request->get; my $scope = '/admin/pay/striptime.tt'; my $vars = {}; return $r->redirect("$LJ::SITEROOT/admin/pay/") unless my $acid = $r->get_args->{from}; if ( $r->did_post ) { my $dbh = LJ::get_db_writer(); my $ct = $dbh->do( 'DELETE FROM shop_codes WHERE acid = ?', undef, $acid ); return error_ml( "$scope.error.db", { err => $dbh->errstr } ) if $dbh->err; return error_ml("$scope.error.stripfail") unless $ct > 0; return success_ml("$scope.success"); } $vars->{acid} = $acid; return DW::Template->render_template( 'admin/pay/striptime.tt', $vars ); } # very sad generic table dumper (generates HTML) my $dump = sub { my ( $sql, @bind ) = @_; my $body = ''; # make an educated guess at durl-ing something my $durl = sub { my $val = $_[0]; my $hr; my $out = sub { foreach (qw/ SIGNATURE USER PWD ccnumber password username /) { $hr->{$_} = 'redacted' if exists $hr->{$_}; } return join( '
', map { "$_: $hr->{$_}" } sort keys %$hr ); }; # first see if it's Storable encoded ... eval { my $x = thaw($val); $hr = $x if $x && ref $x eq 'HASH'; }; return $out->() if $hr; # but see if it seems to be a unix time we can convert to a readable one return LJ::mysql_time( $val, 1 ) if $val =~ /^1\d{9}$/; # and now fall back to urlencoded ... return $val unless $val =~ /&/ && $val =~ /=/; LJ::decode_url_string( $val, $hr = {} ); return $out->(); }; my $dbh = LJ::get_db_writer(); my $sth = $dbh->prepare($sql) or return "

Unable to prepare SQL.

"; $sth->execute(@bind); return "

Error executing SQL.

" if $sth->err; my $rows = []; push @$rows, $_ while $_ = $sth->fetchrow_hashref; return "

No records found.

" unless $rows && @$rows; my @cols = sort { $a cmp $b } keys %{ $rows->[0] }; $body .= q{}; foreach my $row (@$rows) { $body .= q{}; } $body .= q{
}; $body .= join( '', @cols ); $body .= q{
}; $body .= join( '', map { $durl->( $row->{$_} ) } @cols ); $body .= q{
}; return $body; }; sub view_controller { my ( $ok, $rv ) = controller( form_auth => 1, privcheck => ['payments'] ); return $rv unless $ok; my $r = DW::Request->get; my $scope = '/admin/pay/vieworder.tt'; my $vars = {}; my $dbh = LJ::get_db_writer(); # see if we need to look up the cart associated with a paid invite code if ( my $code = $r->get_args->{code} ) { my ($acid) = DW::InviteCodes->decode($code); return error_ml("$scope.error.invalidcode") unless $acid; my $cartid = $dbh->selectrow_array( 'SELECT cartid FROM shop_codes WHERE acid = ?', undef, $acid ); return error_ml( "$scope.error.db", { err => $dbh->errstr } ) if $dbh->err; return error_ml("$scope.error.unpaidcode") unless $cartid; return $r->redirect("$LJ::SITEROOT/admin/pay/view?cartid=$cartid"); } my $cartid = $r->get_args->{cartid}; return error_ml("$scope.error.nocartid") unless $cartid; if ( $cartid !~ /^\d+$/ ) { # see if we were given a PayPal transaction id instead $cartid = $dbh->selectrow_array( 'SELECT cartid FROM pp_trans WHERE transactionid = ?', undef, $cartid ); return error_ml("$scope.error.notfound") unless $cartid && $cartid > 0; } my $cart = DW::Shop::Cart->get_from_cartid($cartid); return error_ml("$scope.error.notfound") unless $cart; $cartid = $cart->id; # see if we are being asked to process a check/money order if ( $r->did_post && $r->post_args->{record_cmo} ) { my $received_method = $r->post_args->{paymentmethod}; my $received_notes = LJ::ehtml( $r->post_args->{notes} ); my %valid_method = map { $_ => 1 } qw( cash check moneyorder other ); return error_ml("$scope.error.invalidpay") unless $valid_method{$received_method}; my %notes_method = map { $_ => 1 } qw( check other ); return error_ml("$scope.error.nonotes") if $notes_method{$received_method} && !$received_notes; # record the payment $dbh->do( "INSERT INTO shop_cmo (cartid, paymentmethod, notes) VALUES (?, ?, ?)", undef, $cartid, $received_method, $received_notes ); return error_ml( "$scope.error.db", { err => $dbh->errstr } ) if $dbh->err; $cart->state($DW::Shop::STATE_PAID); # mark cart as paid return $r->redirect("$LJ::SITEROOT/admin/pay/view?cartid=$cartid"); } $vars->{cart} = $cart; $vars->{u} = LJ::load_userid( $cart->userid ); my $classname = $DW::Shop::PAYMENTMETHODS{ $cart->paymentmethod }->{class}; $vars->{classname} = $classname; # attempt to create an engine so we can get more info about the cart if ( defined $classname ) { $vars->{engine} = eval "DW::Shop::Engine::${classname}->new_from_cart( \$cart )"; } $vars->{dump} = sub { $dump->(@_) }; $vars->{widget} = sub { LJ::Widget::ShopCart->render( admin => 1, cart => $_[0] ) }; $vars->{from_epoch} = sub { DateTime->from_epoch( epoch => $_[0] ) }; $vars->{is_pending} = sub { $_[0] == $DW::Shop::STATE_PEND_PAID ? 1 : 0 }; $vars->{cmo_info} = $dbh->selectrow_hashref( "SELECT paymentmethod, notes FROM shop_cmo WHERE cartid = ?", undef, $cartid ); return DW::Template->render_template( 'admin/pay/vieworder.tt', $vars ); } 1;