sunnyman's (sunnyman) wrote in changelog,
sunnyman's
sunnyman
changelog

[ljcom] r11815: LJSUP-11372: [Refactoring] Develop regio...

Committer: vtroitsky
LJSUP-11372: [Refactoring] Develop regional self promo for Ukraine. Final results. Switching to a new Code.
U   trunk/bin/maint/selfpromo.pl
U   trunk/cgi-bin/LJ/Console/Command/SelfPromo.pm
U   trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm
U   trunk/cgi-bin/LJ/Pay/SelfPromo.pm
U   trunk/cgi-bin/LJ/Widget/Shop/View/SelfPromo.pm
U   trunk/templates/Shop/SelfPromo.tmpl
Modified: trunk/bin/maint/selfpromo.pl
===================================================================
--- trunk/bin/maint/selfpromo.pl	2012-04-26 11:49:02 UTC (rev 11814)
+++ trunk/bin/maint/selfpromo.pl	2012-04-26 11:51:17 UTC (rev 11815)
@@ -5,11 +5,16 @@
 use vars qw( %maint );
 
 use LJ::Pay::SelfPromo;
+use LJ::Pay::PromotionSlot;
 
 $maint{'selfpromo_check'} = sub {
-    LJ::Pay::SelfPromo::EntryPromo->check_current_promo;
-    LJ::Pay::SelfPromo::JournalPromo->check_current_promo;
-    LJ::Pay::SelfPromo::CommunityPromo->check_current_promo;
+    # Iterate through all slots available
+    for my $region qw(ua cyr) {
+        for my $type qw(entry journal community) {
+            my $slot = LJ::Pay::PromotionSlot->find_slot(class => PROMO_SELF, type => $type, region => $region);
+            LJ::Pay::SelfPromo->check_current_promotion($slot) if $slot;
+        }
+    }
 };
 
 1;

Modified: trunk/cgi-bin/LJ/Console/Command/SelfPromo.pm
===================================================================
--- trunk/cgi-bin/LJ/Console/Command/SelfPromo.pm	2012-04-26 11:49:02 UTC (rev 11814)
+++ trunk/cgi-bin/LJ/Console/Command/SelfPromo.pm	2012-04-26 11:51:17 UTC (rev 11815)
@@ -5,6 +5,7 @@
 use base qw( LJ::Console::Command );
 
 use LJ::Pay::SelfPromo;
+use LJ::Pay::PromotionSlot qw(:classes);
 
 sub cmd {'selfpromo'}
 
@@ -45,24 +46,24 @@
     my $object  = $args{'object'};
     my $refund = $args{'refund'};
     my $reason = $args{'reason'};
+    my $slot = $args{'slot'};
 
-    my $class = LJ::Pay::SelfPromo->get_class_by_type($type);
-
     # refund defaults to 1
     $refund = 1 unless defined $refund;
 
     my $admin = LJ::get_remote();
 
-    my $message = 'admin cancel: ' . ($type eq 'entry' ? $object->url : $object->display_name);
+    my $message = 'admin cancel: ' . $object->name;
     $message .= ', without refund' unless $refund;
     $message .= '. Reason: '. $reason if $reason;
 
-    LJ::statushistory_add( $type eq 'entry' ? $object->poster : $object, $admin, 'selfpromo', $message );
+    LJ::statushistory_add( $object->owner, $admin, 'selfpromo', $message );
 
-    $class->admin_cancel_promo(
-        'object'  => $object,
-        'admin'  => $admin,
-        'refund' => $refund,
+    $slot->admin_cancel_promo(
+        'slot'      => $slot,
+        'object'    => $object,
+        'admin'     => $admin,
+        'refund'    => $refund,
     );
 }
 
@@ -85,21 +86,18 @@
     # either it's an entry URL, or a username, or something unknown
     my $type;
     my $object_orig = $object;
-    if ( $object =~ m{^http://} ) {
-        $object = LJ::Entry->new_from_url($object);
-        $type = 'entry';
-        unless ( $object && $object->valid ) {
+
+    $object = LJ::Pay::PromotedObject->new_from_url($object);
+    unless ($object) {
+        if ( $object_orig =~ m{^http://} ) {
             return $self->error("No entry found at $object_orig");
-        }
-    } elsif ( $object =~ /^[a-z0-9\-_]+$/i ) {
-        $object = LJ::load_user($object);
-        $type = 'user';
-        unless ($object) {
+        } elsif ( $object_orig =~ /^[a-z0-9\-_]+$/i ) {
             return $self->error("No such user $object_orig");
+        } else {
+            return $self->error("Invalid object $object_orig passed");
         }
-    } else {
-        return $self->error("Invalid object $object passed");
     }
+    $type = $object->type;
 
     my ($norefund, $reason) = 0;
     if (@args_remainder) {
@@ -117,51 +115,53 @@
         return $self->error("Insufficient arguments");
     }
 
+    my $country = 'cyr';        # TODO
+
+
     if ( $cmd eq 'cancel' ) {
         if ( $type eq 'user' ) {
             return $self->error('"cancel" requires personal journal or community') unless ($object->is_community || $object->is_personal);
             $type = $object->is_community ? 'community' : 'journal';
         }
 
-        my $class = LJ::Pay::SelfPromo->get_class_by_type($type);
-        $class->check_current_promo;
-        my $current_promo = $class->current_promo_info;
+        my $slot = LJ::Pay::PromotionSlot->find_slot(class => PROMO_SELF(), type => $type, country => $country);
+        $slot->check_current_promotion;
+        my $current_promotion = $slot->current_promotion;
 
        # use Data::Dumper;
        # return $self->error("Type: $type". Dumper($current_promo));
 
-        unless ($current_promo) {
+        unless ($current_promotion) {
             return $self->error("No $type currently promoted");
         }
 
-        unless ($current_promo->is_promoting($object))
+        unless ($current_promotion->is_promoting($object))
         {
             return $self->error(
                 $object_orig . " is not the $type currently promoted" );
         }
 
-        $self->_cancel_object( 'type' => $type, 'object' => $object, 'refund' => !$norefund, 'reason' => $reason );
+        $self->_cancel_object( 'slot' => $slot, 'type' => $type, 'object' => $object, 'refund' => !$norefund, 'reason' => $reason );
 
         $self->info( $object_orig . ' cancelled successfully' );
         return 1;
     }
 
     if ( $cmd eq 'ban' ) {
-        if ( $object->isa('LJ::User') ) {
-            my $u = $object;
-            $type = $object->is_community ? 'community' : 'journal';
-
+        if ( $type ne PROMO_OBJECT_TYPE_ENTRY() ) {
+            
+            my $u = $object->object;    # LJ::User
+            # check all current promotions
             foreach my $ptype qw(entry journal community) {
+                my $slot = LJ::Pay::PromotionSlot->find_slot(class => PROMO_SELF(), type => $ptype, country => $country);
+                $slot->check_current_promotion;
+                my $current_promo = $slot->current_promotion();
 
-                my $class = LJ::Pay::SelfPromo->get_class_by_type($ptype);
-                $class->check_current_promo;
-                my $current_promo = $class->current_promo_info();
-
                 if ( $current_promo && ( $current_promo->owner->equals($u) || $current_promo->journal->equals($u) || $current_promo->promoter->equals($u)) )
                 {
                     $self->info('The ban will affect the currently promoted '. $ptype . ', so cancelling that.');
                     my $current_object = $current_promo->object();
-                    $self->_cancel_object( 'type' => $ptype, 'object' => $current_object, 'reason' => $reason );
+                    $self->_cancel_object( 'slot' => $slot, 'type' => $ptype, 'object' => $current_object, 'reason' => $reason );
                 }
             }
 
@@ -172,18 +172,18 @@
             return 1;
         }
 
-        if ( $object->isa('LJ::Entry') ) {
-            my $entry = $object;
+        if ( $type eq PROMO_OBJECT_TYPE_ENTRY() ) {
+            my $entry = $object->object;
+            my $slot = LJ::Pay::PromotionSlot->find_slot(class => PROMO_SELF(), type => $type);
 
-            my $class = LJ::Pay::SelfPromo->get_class_by_type($type);
-            $class->check_current_promo;
-            my $current_promo = $class->current_promo_info();
+            $slot->check_current_promotion;
+            my $current_promo = $slot->current_promotion();
 
-            if ( $current_promo && $current_promo->is_promoting($entry) )
+            if ( $current_promo && $current_promo->is_promoting($object) )
             {
                 $self->info('The ban will affect the currently promoted '
                     . 'entry, so cancelling that.');
-                $self->_cancel_object( 'type' => $type, 'object' => $entry, 'reason' => $reason );
+                $self->_cancel_object( 'slot' => $slot, 'type' => $type, 'object' => $object, 'reason' => $reason );
             }
 
             $entry->set_prop( 'selfpromo_banned' => 1 );
@@ -197,15 +197,15 @@
     }
 
     if ( $cmd eq 'unban' ) {
-        if ( $object->isa('LJ::User') ) {
-            $object->clear_prop('selfpromo_banned');
-            LJ::statushistory_add( $object, $admin, 'selfpromo', 'admin unbanned user. Reason: '. $reason );
-            $self->info( $object->display_name . ' unbanned successfully.');
+        if ( $object->type ne PROMO_OBJECT_TYPE_ENTRY() ) {
+            my $user = $object->object;
+            $user->clear_prop('selfpromo_banned');
+            LJ::statushistory_add( $user, $admin, 'selfpromo', 'admin unbanned user. Reason: '. $reason );
+            $self->info( $user->display_name . ' unbanned successfully.');
             return 1;
-        }
-
-        if ( $object->isa('LJ::Entry') ) {
-            $object->set_prop( 'selfpromo_banned' => undef );
+        } else {  # entry
+            my $entry = $object->object;
+            $entry->set_prop( 'selfpromo_banned' => undef );
             LJ::statushistory_add( $object->journal, $admin, 'selfpromo', 'admin unbanned entry: '. $object->url .'. Reason: '. $reason );
             $self->info( $object->url . ' unbanned successfully.');
             return 1;

Modified: trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm
===================================================================
--- trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm	2012-04-26 11:49:02 UTC (rev 11814)
+++ trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm	2012-04-26 11:51:17 UTC (rev 11815)
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use LJ::Pay::SelfPromo;
+use LJ::Pay::PromotedObject;
 
 use base qw( LJ::Pay::Payment::PayItem );
 
@@ -20,28 +20,13 @@
 #    return LJ::Entry->new( $journalid, 'ditemid' => $ditemid );
 #}
 
-
-# TODO (Refactoring): Incapsulate all that logic in PromoObject object + add constructor to create Promo object from the cart item SelfPromo + use methods of Promo object
-
-sub view_ua_ratings {
-     my $remote = shift;
-     my $view_ua_ratings = 0;
-     if (LJ::is_enabled('personal_stats_ua')) {
-         my $country = LJ::GeoLocation->get_country_info_by_ip;
-         $country = $remote->prop('country') if $remote && $country ne 'UA';
-         if ($country eq 'UA') {
-             my $view_ua_ratings_cookie = LJ::Request->cookie('view_ua_ratings')||'Y';
-             my $view_ua_ratings_prop = $remote ? ($remote->prop('view_ua_ratings')||'Y') : 'N';
-             $view_ua_ratings = 1 if $view_ua_ratings_cookie eq 'Y' || $view_ua_ratings_prop eq 'Y';
-         }
-     }
-     return $view_ua_ratings;
-}
-
 sub get_object {
-    #return  LJ::Pay::SelfPromo::Promo->create_from_item($self);
     my ($self) = @_;
+    $self->{promoted_object} ||= LJ::Pay::PromotedObject->new_from_cart_item($self);
+    return $self->{promoted_object};
 
+    # TOREMOVE: obsolete
+
     my $sp_type = $self->get_prop('selfpromo');
     my $journalid = $self->get_prop('selfpromo_journalid');
 
@@ -59,9 +44,10 @@
 
 sub get_object_url {
     my ($self) = @_;
+    $self->{promoted_object} ||= LJ::Pay::PromotedObject->new_from_cart_item($self);
+    return $self->{promoted_object}->object_url;
 
-    #my $promotion_object = LJ::Pay::SelfPromo::Promo->create_from_item($self);
-    # return $promo_object->url;
+    # TOREMOVE: obsolete
 
     my $object = $self->get_object;
     return unless $object;
@@ -70,11 +56,11 @@
 }
 
 sub get_object_owner {
-    # return $promo_object->owner;
     my ($self) = @_;
+    $self->{promoted_object} ||= LJ::Pay::PromotedObject->new_from_cart_item($self);   #cache object creation
+    return $self->{promoted_object}->owner;
 
-    #my $promotion_object = LJ::Pay::Promotion->new_from_item($self);   #cache object creation
-    # return $promo_object->owner;
+    # TOREMOVE: obsolete
 
     my $object = $self->get_object;
     return unless $object;

Modified: trunk/cgi-bin/LJ/Pay/SelfPromo.pm
===================================================================
--- trunk/cgi-bin/LJ/Pay/SelfPromo.pm	2012-04-26 11:49:02 UTC (rev 11814)
+++ trunk/cgi-bin/LJ/Pay/SelfPromo.pm	2012-04-26 11:51:17 UTC (rev 11815)
@@ -4,14 +4,21 @@
 
 use LJ::Pay::Wallet;
 use LJ::Pay::Wallet::Error qw(:codes);
+use LJ::Pay::PromotionSlot qw(:classes);
+use LJ::Pay::PromotedObject qw(:types);
+use LJ::Pay::Promotion;
 use LJ::Pay::SelfPromo::History;
-use LJ::Pay::SelfPromo::EntryPromo;
-use LJ::Pay::SelfPromo::JournalPromo;
-use LJ::Pay::SelfPromo::CommunityPromo;
-use LJ::Pay::SelfPromo::EntryPromoUA;
-use LJ::Pay::SelfPromo::JournalPromoUA;
-use LJ::Pay::SelfPromo::CommunityPromoUA;
 
+#use LJ::Pay::SelfPromo::Lock;
+#use LJ::Pay::SelfPromo::Error;
+
+#use LJ::Pay::SelfPromo::EntryPromo;
+#use LJ::Pay::SelfPromo::JournalPromo;
+#use LJ::Pay::SelfPromo::CommunityPromo;
+#use LJ::Pay::SelfPromo::EntryPromoUA;
+#use LJ::Pay::SelfPromo::JournalPromoUA;
+#use LJ::Pay::SelfPromo::CommunityPromoUA;
+
 use Sys::Hostname qw();
 
 use Exporter;
@@ -55,26 +62,34 @@
     'journal' => 'journal',
 );
   
-sub type { 'entry' }
-sub class { 'entry' };      # TODO: remove that function from base SelfPromo class
-sub country {''}
+sub old_type { 'entry' }
+sub old_class { 'entry' };      # TODO: remove that function from base SelfPromo class
+sub old_country {''}
+
+sub class { PROMO_SELF() }
   
-sub get_class_by_type {
+sub old_get_class_by_type {
     my ($class, $type, $country) = @_;
     $country ||= 'cyr';
     return $classes_map{$country}{$type};
 }
   
-sub get_object_by_type {
+sub old_get_object_by_type {
     my ($class, $type) = @_;
     return $objects_map{$type};
 }
 
 ### HIGH LEVEL INTERFACE ###
 # NOTE: ALL FUNCTIONS HERE HAVE TO PERFORM EXCLUSIVE MODE OPERATIONS !#
-  
-# TODO: specify promo_type : pass slot object
 sub buyout_cost {
+    my ($class, $slot) = @_;
+    if ( my $promotion = $class->current_promotion( $slot, 'require_db' => 1 ) ) {
+        return $promotion->buyout_cost;
+    }
+    return $slot->config->{'min_cost'};  # return minimal price 
+}
+
+sub old_buyout_cost {
     my ($class) = @_;
   
     if ( my $promo = $class->current_promo_info( undef, 'require_db' => 1 ) ) {    # changed to current_promo_info
@@ -101,6 +116,26 @@
         
 =cut
 sub is_object_eligible {
+    my ($class, $slot, $promoted_object, $promoter, $reason_ref) = @_;
+    my $type = $promoted_object->type;
+  
+    unless ($type) {
+        $$reason_ref = 'unknown_object';
+    }
+
+    unless ($slot->type eq $type) {
+        $$reason_ref = 'mismatch_type';
+        return;
+    }
+
+    return unless $promoted_object->is_eligible($promoter, $reason_ref);
+
+    # TODO: Ensure that object is not promoting at the moment in all promos!!!!
+
+    return 1;
+}
+
+sub old_is_object_eligible {
     my ($class, $object, $promoter, $reason_ref) = @_;
     my $type = ref $object;
   
@@ -137,7 +172,167 @@
         8. Update history
 
 =cut
+
 sub buyout {
+    my ( $class, $slot, $promoted_object, $promoter, $price ) = @_;
+
+
+    die "NO SLOT" unless $slot;
+  
+    # Acquire the lock to process buyout exclusive!
+    # If lock is not acquired during TIMEOUT period, system wide default value 10s, then exception raised.
+  
+    my $lock = $class->lock($slot);    # actually use the lock specific for that class, lock()
+
+    $class->raise_error( ERROR_BIDS_HAVE_CHANGED ) unless $lock;
+ 
+    ## check object that we are going to promote
+    do {
+        my $reason;
+        unless ( $class->is_object_eligible( $slot, $promoted_object, $promoter, \$reason ) ) {
+            $class->raise_error( ERROR_OBJECT_INELIGIBLE, $reason );             
+            # TODO: fix error messages, make it class specific
+        }
+    };
+  
+      ## check price
+    do {
+        my $price_additional = $price - $slot->config->{'min_cost'};
+        if ( $price_additional % $slot->config->{'step'} ) {
+            $class->raise_error(ERROR_INVALID_PRICE);
+        }
+
+        my $min_price = $class->buyout_cost($slot, 'require_db' => 1 );
+        unless ( $price >= $min_price ) {
+            LJ::Pay::SelfPromo->debug_msg(
+                "not enough money offered, need at least $min_price");
+
+            $class->raise_error( ERROR_PRICE_INSUFFICIENT, $min_price );
+        }
+
+        my $wallet_balance = int LJ::Pay::Wallet->get_user_balance($promoter);
+        unless ( $wallet_balance >= $price ) {
+            LJ::Pay::SelfPromo->debug_msg(
+                "promoter doesn't actually have that much " .
+                "(has $wallet_balance, tried to buy out for $price)"
+            );
+
+            $class->raise_error( ERROR_WALLET_INSUFFICIENT_FUNDS,
+                $wallet_balance );
+        }
+    };
+
+    # create a cart and pay for it from the user's balance;
+    # this may throw a wallet exception
+    my $cart;
+    my $current_promotion;
+    do {
+
+        LJ::Pay::SelfPromo->debug_msg("creating the cart");
+
+        # calculate how much money goes where for this item:
+        # a part of it goes to the system, a part of it goes to the
+        # user who purchased the selfpromo that was running before the
+        # buyout, and a part of it is a rounding error that
+        # goes to a special account made for these purposes
+        my ( $refund, $refund_promoid, $refund_userid ) = ( 0, 0, 0 );
+        if ( $current_promotion = $class->current_promotion( $slot, 'require_db' => 1 ) ) {
+            $refund         = $current_promotion->calculate_refund();
+            $refund_promoid = $current_promotion->promoid;
+            $refund_userid  = $current_promotion->promoterid; 
+        }
+
+        my $refund_remainder =
+            $refund % $slot->config->{'refund_step'};
+        my $refund_rounded = $refund - $refund_remainder;
+        my $profit         = $price - $refund;
+
+        my $remainder_to =
+            LJ::load_user( $slot->config->{'remainder_receiver'} );
+        my $remainder_userid = $remainder_to ? $remainder_to->userid : 0;
+
+        $cart = $class->create_shop_cart(
+            {   
+                'slot'             => $slot,
+                'cart_owner'       => $promoter,
+                'object'           => $promoted_object,
+                'price'            => $price,
+                'profit'           => $profit,
+                'rcptid'           => $promoter->userid,
+                'refund'           => $refund_rounded,
+                'refund_userid'    => $refund_userid,
+                'refund_promoid'   => $refund_promoid,
+                'remainder'        => $refund_remainder,
+                'remainder_userid' => $remainder_userid,
+                'type'             => 'buyout',
+            }
+        );
+
+        LJ::Pay::SelfPromo->debug_msg("cart created, setting the method");
+
+        # Take the money from the promoter
+        $cart->set_method( LJ::Pay::Method::Wallet->code );
+
+        LJ::Pay::SelfPromo->debug_msg("paying for the cart");
+        LJ::Pay::Wallet->pay_for_cart( $cart->get_payid );
+        LJ::Pay::SelfPromo->debug_msg("paid successfully");
+        # TODO: Catch exceptions from wallet operations!!!
+ 
+    };
+
+    # deliver the cart synchronously, so that changes are applied
+    # immediately; we can do that because you can't schedule
+    # a selfpromo
+    #
+    # this part isn't supposed to throw any exceptions because
+    # all the checks have been done before
+
+    # NOTES: 
+    #   1. There might not be enought LJ tokens in the user's wallet, at the moment when transaction begins.
+    #   2. 
+ 
+    do {
+        $class->debug_msg("delivering the cart/items");
+        $class->debug_msg( "payid=" . $cart->get_payid );
+
+        my ($item) = $cart->get_items;
+
+        LJ::Pay::SelfPromo->debug_msg( "piid=" . $item->get_piid );
+
+        $cart->update( 'used'   => 'Y' );
+        $item->update( 'status' => 'pend' );
+
+        # Perform deliver: deactivate previous selfpromo object / return money to the previous promoter  / create new promotion object
+
+        $class->_deliver_item($slot, $item, $promoter, $current_promotion, $promoted_object);
+
+        LJ::Pay::SelfPromo->debug_msg(
+            "cart and item updated to be ready for delivery");
+
+        local $SIG{'__WARN__'} = sub {
+            my @messages = @_;
+            $class->debug_msg( "delivery warning: ", @messages );
+            warn @messages;
+        };
+
+        # Mark all delivered by calling deliver method
+        my $deliver_res = $item->deliver( $cart, time, sub { } );
+        unless ($deliver_res) {
+            $class->debug_msg("delivery failed");
+            die "delivering item failed, see error log for details";
+        }
+        $cart->mark_complete;
+    };
+
+    LJ::Pay::SelfPromo->debug_msg("all done");
+
+    # Update history ???    
+    LJ::Pay::SelfPromo::History->update();
+
+    return $cart;
+}
+
+sub old_buyout {
     my ( $class, $object, $promoter, $price ) = @_;
   
     # Acquire the lock to process buyout exclusive!
@@ -292,9 +487,203 @@
 
 
 =item _deliver_item
-    Deliver cart item
+    Actually perform all specific actions to cancel/refuns previous promo
+    Parameters:
+    $slot, $item, $rcpt_u, $current_promotion, $promoted_object
 =cut
 sub _deliver_item {
+    my ($class, $slot, $item, $rcpt_u, $current_promotion, $promoted_object) = @_;
+
+    LJ::Pay::SelfPromo->debug_msg("delivery of a selfpromo item requested");
+    LJ::Pay::SelfPromo->debug_msg( "piid=" . $item->get_piid );
+
+    # this should be temporary until we l10n the shop
+    my $lang = ($rcpt_u && $rcpt_u->prop('browselang')) ? $rcpt_u->prop('browselang') : $LJ::DEFAULT_LANG;
+    my $ml = sub {
+        my ( $code, $params ) = @_;
+        return LJ::Lang::get_text( $lang, $code, undef, $params );
+    };
+
+    my $object_type = $slot->type;
+
+    my $type = $item->get_prop('selfpromo_type'); # action type
+
+    LJ::Pay::SelfPromo->debug_msg( "TEST" );
+
+    LJ::Pay::SelfPromo->debug_msg("type = $type, object_type = $object_type, class = $class");
+    # if there's an entry being promoted, deactivate it, and
+    # then handle any refund if need be
+    #
+    # note that it also stays valid for admin or poster cancellations because they
+    # also only cancel the current promotion
+    my $object_url;
+    if ( $current_promotion ) {
+        LJ::Pay::SelfPromo->debug_msg("current PROMO received");
+        $object_url = $current_promotion->object_url;
+        my $object = $current_promotion->promoted_object;
+        $current_promotion->deactivate(
+            'reason'  => 'buyout',
+            'details' => $item->get_piid,
+        );
+        # Refund some tokens to previous promoter + send notifications
+        if ( my $refund_userid = $item->get_prop('selfpromo_refund_userid') ) {
+            my $refund_to = LJ::load_userid($refund_userid);
+            my $refund = $item->get_prop('selfpromo_refund');
+            if ($refund) {
+                LJ::Pay::SelfPromo->debug_msg("crediting them");
+
+                # credit them:
+                LJ::Pay::Wallet->try_add( $refund_to, $refund );
+                LJ::Pay::Wallet::Log->log(
+                    'userid'   => $refund_userid,
+                    'action'   => LJ::Pay::Wallet::Log::ACTION_ADD,
+                    'qty'      => $refund,
+                    'payid'    => $item->get_payid,
+                    'piid'     => $item->get_piid,
+                    'status'   => LJ::Pay::Wallet::Log::STATUS_FINISHED,
+                    'time_end' => time,
+                );
+
+                LJ::Pay::SelfPromo->debug_msg("successfully credited");
+            }
+
+            my $reason_map = {
+                'buyout'        => 'buyout',
+                'admin_cancel'  => 'admin',
+                'owner_cancel' => 'owner',
+            };
+
+            # email them:
+            $class->send_notification(
+                $slot, $refund_to, 'deactivate',
+                'reason'        => $reason_map->{$type},
+                'object_url'    => $object_url,
+                'object'        => $object,
+                'owner'         => $current_promotion->owner,
+                'refund_amount' => $refund,
+                'duration'      => time - $current_promotion->start_time,
+            );
+
+            unless ( LJ::u_equals($current_promotion->owner, $refund_to) ) {
+                $class->send_notification(
+                    $slot, $current_promotion->owner, 'deactivate',
+                    'reason'        => $reason_map->{$type},
+                    'object_url'    => $object_url,
+                    'object'        => $object,
+                    'refund_amount' => $refund,
+                    'duration'      => time - $current_promotion->start_time,
+                    'notify_owner' => 1,
+                );
+            }
+            LJ::Pay::SelfPromo->debug_msg("refund successful");
+        }
+    }
+
+    # if there's any 'remainder' tokens we need to send, go send them:
+    if ( my $remainder = $item->get_prop('selfpromo_remainder') ) {
+        my $remainder_to_userid =
+            $item->get_prop('selfpromo_remainder_userid');
+        my $remainder_to = LJ::load_userid($remainder_to_userid);
+
+        if ($remainder_to) {
+            LJ::Pay::SelfPromo->debug_msg(
+                "sending the remainder of tokens to " .
+                $remainder_to->username );
+
+            LJ::Pay::Wallet->try_add( $remainder_to, $remainder );
+            LJ::Pay::Wallet::Log->log(
+                'userid'   => $remainder_to_userid,
+                'action'   => LJ::Pay::Wallet::Log::ACTION_ADD,
+                'qty'      => $remainder,
+                'payid'    => $item->get_payid,
+                'piid'     => $item->get_piid,
+                'status'   => LJ::Pay::Wallet::Log::STATUS_FINISHED,
+                'time_end' => time,
+            );
+        }
+
+    }
+
+    # now that we've handled all the deactivations, refunds, and remainders,
+    # we will need to determine if we need to activate anything,
+    # and what to email, which really depends on the item type
+    my ( $email_subject, $email_body );
+
+    if ( $type eq 'buyout' ) {
+
+        my $object = $promoted_object;
+        my $owner = $promoted_object->owner;
+        my $object_url = $promoted_object->url;         # New promo object URL
+ 
+        my $cost = $item->get_amt * LJ::Pay::Wallet::EXCHANGE_RATE();
+        
+        my $new_promotion = LJ::Pay::Promotion::Auction->create_active($slot, $promoted_object, $cost, $rcpt_u);
+
+        $item->set_subitem(join('-', ($slot->slot_id, $new_promotion->promoid)));
+        $item->save();
+
+        $email_subject = $ml->("selfpromo.$object_type.notification.activate.subject");
+ 
+        LJ::Pay::SelfPromo->debug_msg("generating the emails");
+        # Need to send second email if object owner and recipient are different
+        unless ( LJ::u_equals($owner, $rcpt_u) ) {
+            $email_body = $ml->(
+                "selfpromo.$object_type.notification.activate.body",
+                {   'user'    => $owner->display_name,
+                    'object_url' => $object_url,
+                },
+            );
+
+            LJ::send_mail(
+                {   'to'       => $owner->email_raw,
+                    'from'     => $LJ::DONOTREPLY_EMAIL,
+                    'fromname' => $LJ::SITENAME,
+                    'subject'  => $email_subject,
+                    'body'     => $email_body,
+                }
+            );
+        }
+
+        $email_body = $ml->(
+            "selfpromo.$object_type.notification.activate.body",
+            {   'user'    => $rcpt_u->display_name,
+                'object_url' => $object_url,
+            },
+        );
+    }
+    elsif ( $type eq 'admin_cancel' ) {
+
+        # we don't need to activate anything, but the admin
+        # gets notified that everything was cancelled
+        # successfully
+
+        $email_subject = $ml->("selfpromo.$object_type.notification.admin_cancel.subject");
+
+        $email_body = $ml->(
+            "selfpromo.$object_type.notification.admin_cancel.body",
+            {   'user'     => $rcpt_u->display_name,
+                'object_url' => $current_promotion ? $object_url : '',
+            },
+        );
+    }
+
+    if ($email_subject && $email_body) {
+        LJ::send_mail(
+            {  'to'        => $rcpt_u->email_raw,
+                'from'      => $LJ::ACCOUNTS_EMAIL,
+                'fromname'  => $LJ::SITENAMESHORT,
+                'subject'   => $email_subject,
+                'body'      => $email_body,
+                'wrap'      => 1,
+                'charset'   => 'utf-8',
+            }
+        );
+    }
+
+    LJ::Pay::SelfPromo->debug_msg("delivery successful");
+}
+
+sub _old_deliver_item {
     my ($class, $item, $rcpt_u) = @_;
 
     LJ::Pay::SelfPromo->debug_msg("delivery of a selfpromo item requested");
@@ -480,7 +869,6 @@
         );
     }
 
-
     if ($email_subject && $email_body) {
         LJ::send_mail(
             {  'to'        => $rcpt_u->email_raw,
@@ -496,13 +884,104 @@
 
     LJ::Pay::SelfPromo->debug_msg("delivery successful");
 }
- 
+
 =item admin_cancel_promo
    Admin function to cancel current promo        
 =cut
 sub admin_cancel_promo {
     my ( $class, %args ) = @_;
 
+    my $promoted_object = $args{'object'};
+    my $admin = $args{'admin'};
+    my $slot = $args{'slot'};
+
+    # TODO: Acquire lock to perform cancel action!
+    my $lock = $class->lock($slot);
+    # TODO: Perform lock timeout processing
+
+    my $promotion = $class->current_promotion($slot, 'require_db' => 1 );                        
+    unless ( $promotion && $promotion->is_promoting($promoted_object) )
+    {
+        # your princess is in another castle, sorry
+        # TODO: return an error
+        return;
+    }
+
+    # create a cart and set it as free, and then mark it as pending
+    # delivery
+    my $cart;
+    do {
+
+        # calculate how much money goes where for this item:
+        # none it goes to the system, a part of it goes to the
+        # user who purchased the selfpromo being cancelled,
+        # and a part of it is a rounding error that goes to a
+        # special account made for these purposes
+        my $refund = 0;
+        if ( $args{'refund'} ) {
+            $refund = $promotion->calculate_refund();
+        }
+
+        my $refund_promoid = $promotion->promoid;
+        my $refund_userid  = $promotion->promoterid;
+
+        my $refund_remainder =
+            $refund % $slot->config->{'refund_step'};
+        my $refund_rounded = $refund - $refund_remainder;
+
+        my $remainder_to =
+            LJ::load_user( $slot->config->{'remainder_receiver'} );
+        my $remainder_userid = $remainder_to ? $remainder_to->userid : 0;
+
+        $cart = $class->create_shop_cart(
+            {
+                'slot'             => $slot,
+                'cart_owner'       => $admin,
+                'object'           => undef,
+                'price'            => 0,
+                'profit'           => 0,
+                'rcptid'           => $admin->userid,
+                'refund'           => $refund_rounded,
+                'refund_userid'    => $refund_userid,
+                'refund_promoid'   => $refund_promoid,
+                'remainder'        => $refund_remainder,
+                'remainder_userid' => $remainder_userid,
+                'type'             => 'admin_cancel',
+            }
+        );
+
+        # mark this cart as paid for
+        $cart->set_method( LJ::Pay::Method::Free->code );
+        $cart->update( 'used' => 'N', 'mailed' => 'N' );
+        $cart->set_daterecv_epoch(time);
+    };
+
+    # deliver the cart synchronously, so that changes are applied
+    # immediately; we can do that because you can't schedule
+    # a selfpromo
+    #
+    # this part isn't supposed to throw any exceptions because
+    # all the checks have been done before
+
+    do {
+        my ($item) = $cart->get_items;
+
+        $cart->update( 'used'   => 'Y' );
+        $item->update( 'status' => 'pend' );
+
+        $class->_deliver_item($slot, $item, $admin, $promotion, undef);
+
+        $item->deliver( $item->get_cart, time, sub { } );
+
+        $cart->mark_complete();
+    };
+
+    return 1;
+}
+
+sub old_admin_cancel_promo {
+    my ( $class, %args ) = @_;
+
     my $object = $args{'object'};
     my $admin = $args{'admin'};
 
@@ -592,8 +1071,80 @@
 }
 
 # owner of the promoted object cancel promotion with refunding to the promotera
-# TODO: Move to LOW LEVEL interface
+# obsolete
 sub owner_cancel_promo {            # the name of the function has been changed from poster_cancel_promo
+    my ( $class, $slot, $promoted_object ) = @_;
+
+    return unless $promoted_object;
+
+    # TODO: Acquire lock to perform cancelling!
+    my $lock = $class->lock($slot);
+    # TODO: Perform error processing when lock couldn't be acquired during default timeout, 10s
+
+    my $promotion = $class->current_promotion( $slot, 'require_db' => 1 );
+    use Data::Dumper;
+
+  #  warn "CP:".Dumper($promotion);
+
+    unless ( $promotion && $promotion->is_promoting($promoted_object) )
+    {
+        # your princess is in another castle, sorry
+        # TODO: return error
+        die "ERRR";
+        return;
+    }
+
+    my $owner = $promotion->owner;
+
+    my $cart;
+    my $refund = $promotion->calculate_refund();
+    my $promoter = $promotion->promoter;
+
+    my $refund_remainder =
+        $refund % $slot->config->{'refund_step'};
+
+    my $refund_rounded = $refund - $refund_remainder;
+
+    my $remainder_to =
+        LJ::load_user( $slot->config->{'remainder_receiver'} );
+    my $remainder_userid = $remainder_to ? $remainder_to->userid : 0;
+
+    $cart = $class->create_shop_cart(
+        {
+            'slot'             => $slot,
+            'cart_owner'       => $owner,
+            'object'           => undef,
+            'price'            => 0,
+            'profit'           => 0,
+            'rcptid'           => $owner->userid,
+            'refund'           => $refund_rounded,
+            'refund_userid'    => $promoter->userid,
+            'refund_promoid'   => $promotion->promoid,
+            'remainder'        => $refund_remainder,
+            'remainder_userid' => $remainder_userid,
+            'type'             => 'owner_cancel',
+        }
+    );
+
+    $cart->set_method( LJ::Pay::Method::Free->code );
+    $cart->update( 'used' => 'N', 'mailed' => 'N' );
+    $cart->set_daterecv_epoch(time);
+
+    my ($item) = $cart->get_items;
+
+    $cart->update( 'used'   => 'Y' );
+    $item->update( 'status' => 'pend' );
+
+    $class->_deliver_item($slot, $item, $owner, $promotion, undef);
+
+    $item->deliver( $item->get_cart, time, sub { } );
+
+    $cart->mark_complete;
+
+    return 1;
+}
+
+sub old_owner_cancel_promo {            # the name of the function has been changed from poster_cancel_promo
     my ( $class, $object ) = @_;
 
     return unless $object;
@@ -666,8 +1217,86 @@
 # REFACTORING:
 #   1. Pass slot
 #   2. 
+sub withdraw_object {
+    my ( $class, $slot, $promoted_object ) = @_;
+  
+    # TODO? : Acquire lock to perform cancelling operation on selfpromo
+    # TODO: Add errors processing in case of not acquiring lock during default timeout.
+    my $lock = $class->lock($slot);
+
+    # So we are actually here in case we've got lock successfully!!!
+
+    my $promotion = $class->current_promotion( $slot, 'require_db' => 1 );
+    return unless $promotion;       # do nothing & return undef in case of 
  
-sub withdraw_object {
+    my $owner = $promotion->owner;  # get the owner if the cucrrently promoted object
+    my $promoter = $promotion->promoter;    # get the promoter object
+    my $object_url = $promotion->object_url;
+  
+    # TODO??? : return undef is better?
+    # TODO: Here better raise common error
+    die 'promoted object mismatch'
+        unless $promotion && $promotion->is_promoting($promoted_object);
+  
+    unless ( LJ::u_equals($owner, $promoter) ) {
+
+        my $remote = LJ::get_remote();              # TODO: To remove dependency from remote user !!!
+  
+          # Owner cancels promo, need to pay refund to promoter
+        unless ( LJ::u_equals($remote, $promoter) ) {
+            # Req: $owner == $remote
+            # Notifications will be sent via shop
+            # TODO: ensure $owner == $remote , it's not obvious from conditions, it has to be concluded!
+#            return $class->owner_cancel_promo($object);
+
+            # send notifications to the promoter (owner cancelled)
+            $class->send_notification(
+                $slot, $promoter, 'deactivate',
+                    'reason'        => 'owner',
+                    'object_url'    => $object_url,
+                    'object'        => $promoted_object,
+                    'owner'         => $owner,
+                    'duration'      => time - $promotion->start_time,
+            );
+
+            # send notification to the owner itself (owner cancelled)
+            $class->send_notification(
+                    $slot, $promotion->owner, 'deactivate',
+                    'reason'        => 'owner',
+                    'object_url'    => $object_url,
+                    'object'        => $promoted_object,
+                    'duration'      => time - $promotion->start_time,
+                    'notify_owner' => 1,
+            );
+
+            $promotion->deactivate( 'reason' => 'withdraw' );
+            return 1;
+
+        } else {
+            $class->send_notification(  # send notification to the owner of the promoted object
+                  $slot, $owner, 'deactivate',
+                  'reason'    => 'withdraw',
+                  'object_url' => $object_url,
+                  'duration'  => $promotion->stop_time - $promotion->start_time,
+                  'notify_owner' => 1,   
+            );
+        }
+    }
+  
+    # Send notification to promoter
+    $class->send_notification(
+          $slot, $promoter, 'deactivate',
+          'reason'    => 'withdraw',
+          'object_url' => $object_url,
+          'duration'  => $promotion->stop_time - $promotion->start_time,
+    );
+ 
+    $promotion->deactivate( 'reason' => 'withdraw' );
+  
+    return 1;
+}
+
+sub old_withdraw_object {
     my ( $class, $object ) = @_;
   
     # TODO? : Acquire lock to perform cancelling operation on selfpromo
@@ -767,6 +1396,13 @@
 # return current promoted object: 
 # So it might be: entry / journal / community
 sub current_promoted_object {
+    my ($class, $slot, %opts) = @_;
+    my $promotion = $class->current_promotion($slot, %opts);
+    return unless $promotion;
+    return $promotion->promoted_object;
+}
+
+sub old_current_promoted_object {
     my ($class, %opts) = @_;
     my $promo = $class->current_promo_info(undef, %opts);
     return unless $promo;
@@ -777,7 +1413,98 @@
 # deactivate it: depending on cases
 #sub check_current_entry {
 # Called from worker 
-sub check_current_promo {
+
+sub check_current_promotion {
+    my ($class, $slot) = @_;
+  
+    # Acquire lock to exclusive change promotion status
+    # TODO: Perform error processing in case we can't acquire the lock during default timeout.
+    my $lock = $class->lock($slot);
+    #  return unless $lock;
+  
+    my $promotion = $class->current_promotion( $slot, 'require_db' => 1 );
+    return unless $promotion;    # everything's fine if nothing's promoted
+  
+    my $promoted_object = $promotion->promoted_object;   # promoted object: entry / user(community/journal)
+    my $owner  = $promotion->owner;             # owner of the promoted object: user 
+    my $promoter = $promotion->promoter;        # promoter: user
+    my $object_url = $promotion->object_url;
+
+    if ($promotion->is_object_deleted) {  
+  
+        # we no longer have an entry because it's been deleted,
+        # so let's reconstruct the URL => get it from promo object
+  
+        $promotion->deactivate( 'reason' => 'deleted' );
+        $class->send_notification(
+            $slot, $promoter, 'deactivate',
+            'reason'    => 'deleted',
+            'object_url' => $object_url,
+            'duration'  => time - $promotion->start_time,
+        );
+  
+        unless ( LJ::u_equals($owner, $promoter) ) {
+            $class->send_notification(
+                $slot, $owner, 'deactivate',
+                'reason'    => 'deleted',
+                'object_url' => $object_url,
+                'duration'  => time - $promotion->start_time,
+                'notify_owner' => 1,
+            );
+        }
+    }
+  
+    my $reason;
+
+    unless ( $promoted_object->is_eligible( $promoter, \$reason ) ) {
+        $promotion->deactivate(
+            'reason'  => 'ineligible',
+            'details' => $reason,
+        );
+
+        $class->send_notification(
+            $slot, $promoter, 'deactivate',
+            'reason'    => 'ineligible',
+            'details'   => $reason,
+            'object_url' => $object_url,
+            'duration'  => time - $promotion->start_time,
+        );
+
+        unless ( LJ::u_equals($owner, $promoter) ) {
+            $class->send_notification(
+                $slot, $owner, 'deactivate',
+                'reason'    => 'ineligible',
+                'details'   => $reason,
+                'object_url' => $object_url,
+                'duration'  => time - $promotion->start_time,
+                'notify_owner' => 1,
+            );
+        }
+    }
+
+    unless ( $promotion->stop_time > time ) {
+        $promotion->deactivate( 'reason' => 'expired' );
+
+        $class->send_notification(
+            $slot, $promoter, 'deactivate',
+            'reason'    => 'expired',
+            'object_url' => $object_url,
+            'duration'  => time - $promotion->start_time,
+        );
+
+        unless ( LJ::u_equals($owner, $promoter) ) {
+            $class->send_notification(
+                $slot, $owner, 'deactivate',
+                'reason'    => 'expired',
+                'object_url' => $object_url,
+                'duration'  => time - $promotion->start_time,
+                'notify_owner' => 1,
+            );
+        }
+    }
+}
+
+sub old_check_current_promo {
     my ($class) = @_;
   
     # Acquire lock to exclusive change promotion status
@@ -892,95 +1619,63 @@
     }
 }
 
-sub find_promos {
-    my ( $class, $params ) = @_;
+### LOW LEVEL INTERFACE ###
+# BEWARE NOTE: All functions here don't perform exclusive operations, no locks and mutexes!
+  
+=item current_promotion
+    return current promotion objct
+=cut
+sub current_promotion {
+    my ($class, $slot, %opts) = @_;
 
-    my @where = (1);
-    my @binds;
-    my ( $sort_cond, $limit_cond, $offset_cond ) = ( '', '', '' );
+  #  warn "current_promotion";
+    die "NO SLOT" unless($slot);
 
-    # Find only one class of promos
-    push @where, 'type = ?';
-    push @binds, $class->type;
+    my $promoid = $opts{'promoid'};
 
-    push @where, 'country = ?';
-    push @binds, $class->country;
+    unless ($promoid) {
+        my $from_memcache;
 
-    if ( my $started_min = delete $params->{'started_min'} ) {
-        push @where, 'started > ?';
-        push @binds, $started_min;
-    }
+        unless ( $opts{'require_db'} ) {
+            $from_memcache = LJ::MemCache::get( $slot->memcache_active_key );
+        }
 
-    if ( my $finished_min = delete $params->{'finished_min'} ) {
-        push @where, '(finished > ?'. (delete $params->{'unfinished'}? ' OR active = 1' : ''). ')';
-        push @binds, $finished_min;
-    }
+        # from_memcache = undef means there's nothing in memcache
+        # (or it's disabled in the options), so we need to hit DB;
+        # from_memcache = 0 means 'no current entry';
+        # from_memcache = HASH(0xDEADBEEF) means bless it and return
+        if ( defined $from_memcache ) {
+            return unless $from_memcache;
+            return LJ::Pay::Promotion::Auction->new_from_row($from_memcache);
+        }
+        my $rows = LJ::Pay::Promotion->find_promotions(         # perform select for class specific rows
+            { 'slot' => $slot, 'require_active' => 1, 'return_rows' => 1 } );
 
-    if ( my $started_max = delete $params->{'started_max'} ) {
-        push @where, 'started < ?';
-        push @binds, $started_max;
-    }
+        unless ($rows && @$rows) {
+            LJ::MemCache::set( $slot->memcache_active_key, 0, 1800 );
+            return;
+        }
 
-    if ( my $promoid = delete $params->{'promoid'} ) {
-        push @where, 'promoid = ?';
-        push @binds, $promoid;
-    }
+        unless ( @$rows == 1 ) {
+            warn 'more than 1 entry in selfpromo, data corrupt?';
+        }
 
-    if ( my $promoid_max = delete $params->{'promoid_max'} ) {
-        push @where, 'promoid < ?';
-        push @binds, $promoid_max;
+        my $row = $rows->[0];
+        LJ::MemCache::set( $slot->memcache_active_key, $row, 1800 );
+        return LJ::Pay::Promotion::Auction->new_from_row($row);
     }
 
-    if ( my $posterid = delete $params->{'posterid'} ) {
-        push @where, 'posterid = ?';
-        push @binds, $posterid;
-    }
+    return do {
+        my $promos = LJ::Pay::Promotion->find_promotions( { 'slot' => $slot, 'promoid' => $promoid } );
 
-    if ( delete $params->{'require_active'} ) {
-        push @where, 'active = 1';
-    }
+        return unless @$promos;
+        return $promos->[0];
+    };
 
-    if ( my $sort = delete $params->{'sort'} ) {
-        $sort_cond = "ORDER BY $sort";
-    }
-
-    if ( my $limit = delete $params->{'limit'} ) {
-        $limit_cond = "LIMIT $limit";
-    }
-
-    if ( my $offset = delete $params->{'offset'} ) {
-        $offset_cond = "OFFSET $offset";
-    }
-
-    if ( my $ditemid = delete $params->{'ditemid'} ) {
-        push @where, 'ditemid = ?';
-        push @binds, $ditemid;
-    }
-    my $where_sql = join( ' AND ', @where );
-
-    my $dbh = LJ::get_db_writer();
-    my $rows = $dbh->selectall_arrayref(
-        "SELECT * FROM selfpromo WHERE $where_sql $sort_cond $limit_cond $offset_cond",
-        { 'Slice' => {} }, @binds,
-    );
-
-    return $rows if $params->{'return_rows'};
-
-    my $promos = [ map { LJ::Pay::SelfPromo::Promo->new($_) } @$rows ];
-    return $promos;
 }
 
-### LOW LEVEL INTERFACE ###
-# BEWARE NOTE: All functions here don't perform exclusive operations, no locks and mutexes!
-  
-#sub check_object {
-#    my ($class, $object) = @_;
-#    return undef unless UNIVERSAL::isa($entry, 'LJ::Entry');
-#    return 1;
-#}
-
 # return object LJ::Pay::SelfPromo::Promo, make subclasses?
-sub current_promo_info {
+sub old_current_promo_info {
     my ($class, $promoid, %opts) = @_;
 
     unless ($promoid) {
@@ -1032,9 +1727,9 @@
 # * it doesn't send any notifications
 #
 # called from LJ::Pay::Payment::PayItem::SelfPromo
-# Actually creates new selfpromo object / active row in DB
-  
-sub activate_object {
+
+# Actually creates new selfpromo object / active row in DBa
+sub old_activate_object {
     my ( $class, $object, $cost, $promoter ) = @_;
   
     # ????: Before activate the new object, check current promo????  gain lock ???
@@ -1098,12 +1793,10 @@
     );
 }
 
-
 # supported opts: reason, details, object, refund
 #
 # called from LJ::Pay::Payment::PayItem::SelfPromo
-
-sub deactivate_object {
+sub old_deactivate_object {
     my ( $class, %opts ) = @_;
   
     my $promo = $class->current_promo_info(undef, 'require_db' => 1);
@@ -1133,9 +1826,10 @@
     );
 }
 
+
 # supported opts: reason, details, object, refund
 # TODO: we have no specify promoid
-sub deactivate_promo {
+sub old_deactivate_promo {
     my ( $class, $promoid, $promoter, %opts ) = @_;
 
     $class->db_update_promo( $promoid, 'active' => 0, 'finished' => time );
@@ -1164,7 +1858,7 @@
 # * refund_promoid
 # * remainder
 # * remainder_userid
-# * entry
+# * object
 sub create_shop_cart {
     my ( $class, $opts ) = @_;
 
@@ -1173,6 +1867,60 @@
 
     my $cart = LJ::Pay::Payment::new_cart( $opts->{'cart_owner'} );
 
+    $cart->payvar_append('do_not_notify_recepient', 1);
+
+    my $it = LJ::Pay::Payment::PayItem->new_memonly(
+        'item'    => 'selfpromo',
+        'subitem' => '',
+        'amt'     => $opts->{'price'} / LJ::Pay::Wallet::EXCHANGE_RATE,
+        'qty'     => 1,
+        'anon'    => 0,
+        'rcptid'  => $opts->{'rcptid'},
+    );
+
+    $cart->add_item(%$it);
+
+    ($it) = $cart->get_items;
+
+    # set payitem props as appropriate
+    $it->set_prop( 'selfpromo'                => $opts->{'slot'}->type );
+    $it->set_prop( 'selfpromo_type'           => $opts->{'type'} );
+    $it->set_prop( 'selfpromo_profit'         => $opts->{'profit'} );
+    $it->set_prop( 'selfpromo_refund'         => $opts->{'refund'} );
+    $it->set_prop( 'selfpromo_refund_userid'  => $opts->{'refund_userid'} );
+    $it->set_prop( 'selfpromo_refund_promoid' => $opts->{'refund_promoid'} );
+    $it->set_prop( 'selfpromo_remainder'      => $opts->{'remainder'} );
+    $it->set_prop( 
+                 'selfpromo_remainder_userid' => $opts->{'remainder_userid'} );
+
+    # Add object specific properties, if object exists (in case of non refunding cart)
+    if ( my $object = $opts->{'object'} ) {
+
+        my $ownerid = $object->ownerid;
+        $it->set_prop( 'selfpromo_journalid' => $ownerid );
+
+        if ( $object->type eq PROMO_OBJECT_TYPE_ENTRY() ) {
+            $it->set_prop( 'selfpromo_ditemid'   => $object->ditemid );
+        }
+    }
+
+    LJ::Pay::SelfPromo->debug_msg(
+        "create_shop_cart successful, " .
+        "payid=" . $cart->get_payid . ", " .
+        "piid=" . $it->get_piid
+    );
+
+    return $cart;
+}
+
+sub old_create_shop_cart {
+    my ( $class, $opts ) = @_;
+
+    LJ::Pay::SelfPromo->debug_msg(
+        "create_shop_cart called: " . LJ::compact_dumper($opts) );
+
+    my $cart = LJ::Pay::Payment::new_cart( $opts->{'cart_owner'} );
+
     $cart->payvar_append('do_not_notify_recepient',1);
 
     my $it = LJ::Pay::Payment::PayItem->new_memonly(
@@ -1224,6 +1972,91 @@
 
 # TODO (Refactoring):  Pass PromoObject instead of %opts and use methods
 sub send_notification {
+    my ( $class, $slot, $user, $action, %opts ) = @_;
+  
+    my ( $ml_var, %ml_params );
+    my $sp_class = $slot->type();  # type of objects promoted in that slot
+  
+    if ( $action eq 'deactivate' ) {
+        my $reason = $opts{'reason'};
+        my $object = $opts{'object'};   # note that it may be undefined, especially for entries!!!!
+  
+        %ml_params = (
+            'refund_amount'  => $opts{'refund_amount'},
+            'object_url'     => $opts{'object_url'},
+            'duration_hours' => int( $opts{'duration'} / 3600 ),
+            'duration_min'   => int( ( $opts{'duration'} % 3600 ) / 60 ),
+            'user'           => $user->display_name,
+        );
+  
+        if ( $reason eq 'admin' ) {
+            if ( $opts{'notify_owner'} ) {
+                $ml_var = "selfpromo.$sp_class.notification.deactivate.admin3";
+            } else {
+                $ml_var = "selfpromo.$sp_class.notification.deactivate.admin2";
+                unless ( $opts{'refund_amount'} > 0 ) {
+                    $ml_var .= '.norefund';
+                }
+                $ml_params{'owner'} = $opts{'owner'} ? $opts{'owner'}->display_name : '';
+            }
+        }
+
+        if ( $reason eq 'buyout' ) {
+            if ( $opts{'notify_owner'} ) {
+                $ml_var = "selfpromo.$sp_class.notification.deactivate.buyout3";
+            } else {
+                $ml_var = "selfpromo.$sp_class.notification.deactivate.buyout2";
+                $ml_params{'owner'} = $opts{'owner'} ? $opts{'owner'}->display_name : '';
+            }
+        }
+
+        if ( $reason eq 'deleted' ) {
+            $ml_var = "selfpromo.$sp_class.notification.deactivate.deleted";
+        }
+
+        if ( $reason eq 'expired' ) {
+            $ml_var = "selfpromo.$sp_class.notification.deactivate.expired";
+        }
+
+        if ( $reason eq 'ineligible' ) {
+            my $details = $opts{'details'};
+            $ml_var = "selfpromo.$sp_class.notification.deactivate.ineligible.$details";
+        }
+
+        if ( $reason eq 'withdraw' ) {
+            $ml_var = "selfpromo.$sp_class.notification.deactivate.withdraw";
+        }
+
+        if ( $reason eq 'owner' ) {
+            if ( $opts{'notify_owner'} ) {
+                $ml_var = "selfpromo.$sp_class.notification.deactivate.withdraw";
+            } else {
+                $ml_var = "selfpromo.$sp_class.notification.deactivate.owner";
+                $ml_params{'promoter'} = $ml_params{'user'};
+                $ml_params{'owner'} = $opts{'owner'} ? $opts{'owner'}->display_name : '';
+            }
+        }
+    }
+
+    my $lang = $user->prop('browselang') || $LJ::DEFAULT_LANG;
+    my $subject =
+        LJ::Lang::get_text( $lang, "$ml_var.subject", undef,
+        \%ml_params, );
+
+    my $body = LJ::Lang::get_text( $lang, "$ml_var.body", undef,
+        \%ml_params, );
+
+    LJ::send_mail(
+        {   'to'       => $user->email_raw,
+            'from'     => $LJ::DONOTREPLY_EMAIL,
+            'fromname' => $LJ::SITENAME,
+            'subject'  => $subject,
+            'body'     => $body,
+        }
+    );
+}
+
+sub old_send_notification {
     my ( $class, $user, $action, %opts ) = @_;
   
     my ( $ml_var, %ml_params );
@@ -1314,7 +2147,20 @@
     );
 }
 
-sub calculate_refund {
+# TODO: Candidate to move it to PromotionSlot
+sub was_promoted {
+    my ($class, $slot, $object) = @_;
+    return undef unless ($object->type eq PROMO_OBJECT_TYPE_ENTRY);
+    return LJ::MemCache::get_or_set(
+        $slot->was_promoted_key($object->ownerid, $object->ditemid), sub {
+            my $res = LJ::Pay::Promotion->find_promotions({ slot => $slot, ditemid => $object->ditemid, posterid => $object->ownerid });
+            return @$res? 1 : 0;
+        }, 7200
+    );
+}
+
+# Moved to LJ::Pay::Promotion::Auction
+sub old_calculate_refund {
     my ( $class, $promo ) = @_;
 
     my $unit        = $LJ::SELF_PROMO_CONF->{$class->class}{'refund_time_unit'};
@@ -1332,7 +2178,8 @@
     return $refund_amount;
 }
 
-sub db_insert_promo {
+# Moved to LJ::Pay::Promotion::Auction
+sub old_db_insert_promo {
     my ( $class, %args ) = @_;
 
     my $dbh = LJ::get_db_writer();
@@ -1349,8 +2196,8 @@
 
     $dbh->do( "INSERT INTO selfpromo SET $sets", undef, @binds );
 }
-
-sub db_update_promo {
+# Moved to LJ::Pay::Promotion::Auction
+sub old_db_update_promo {
     my ( $class, $promoid, %args ) = @_;
 
     my $dbh = LJ::get_db_writer();
@@ -1366,38 +2213,53 @@
         undef, @binds, $promoid, $class->type, $class->country);
 }
 
-sub clear_memcache {
+sub old_clear_memcache {
     my ($class, $ownerid, $objectid) = @_;
     LJ::MemCache::delete( $class->memcache_key );
     LJ::MemCache::delete( $class->was_promoted_key($ownerid, $objectid) ) if $ownerid and $objectid;
 }
 
-sub memcache_key { 
+sub old_memcache_key { 
     my $self = shift; 
     return 'self_promo_'.$self->class().$self->country().'_active';
 }
-sub was_promoted_key { 'self_promo_'.$_[0]->class().$_[0]->country().'_ids_promoted:'. join ':', $_[1], $_[2] }
 
+sub old_was_promoted_key { 'self_promo_'.$_[0]->class().$_[0]->country().'_ids_promoted:'. join ':', $_[1], $_[2] }
+
 sub raise_error {
     my ( $class, @args ) = @_;
     LJ::Pay::SelfPromo::Error->raise(@args);
 }
 
 sub lock {
+    my ($class, $slot) = @_;
+    return LJ::Pay::SelfPromo::Lock->new($slot ? $slot->slot_id : $class->class());
+}
+
+sub old_lock {
     return LJ::Pay::SelfPromo::Lock->new(shift->class());
 }
 
 sub lock_taken {
+    my ($class, $slot) = @_;
+    return LJ::Pay::SelfPromo::Lock->taken($slot ? $slot->slot_id : $class->class());
+}
+
+sub old_lock_taken {
     return LJ::Pay::SelfPromo::Lock->taken(shift->class());
 }
 
 sub lock_taken_elsewhere {
+    my ($class, $slot) = @_;
+    return LJ::Pay::SelfPromo::Lock->taken_elsewhere($slot ? $slot->slot_id : $class->class());
+}
+
+sub old_lock_taken_elsewhere {
     return LJ::Pay::SelfPromo::Lock->taken_elsewhere(shift->class());
 }
 
-# supported opts: object_url, event, reason, details
-# TODO: Add specific common messages for all types of selfpromo
-sub log {
+# supported opts: object_url, event, reason, detailsh# TODO: Add specific common messages for all types of selfpromo
+sub old_log {
     my ( $class, $promoter, %opts ) = @_;
 
     my $message = '';
@@ -1431,95 +2293,6 @@
     warn "$msg in sub ${package}::${sub} at $filename line $line\n";
 }
 
-# TODO(2nd refactoring stage): Roadway: Incapsulate here more logic from all other parts (look through all TODO remarks in the code)
-# 1. Incapsulate all construction methods here.
-# 2. Incapsulate promo history storage logic here.
-# 3. Add object state
-package LJ::Pay::SelfPromo::Promo;
-
-use base qw( Class::Accessor );
-
-__PACKAGE__->mk_accessors(
-    qw(
-        promoid journalid posterid jitemid ditemid
-        started exptime cost active finished promoterid type
-    )
-);
-
-sub new {
-    my ( $class, $data ) = @_;
-    return bless $data, $class;
-}
-
-sub journal {
-    my ($self) = @_;
-    return LJ::load_userid( $self->journalid );
-}
-
-# TODO: Entry specific
-sub poster {
-    my ($self) = @_;
-    return LJ::load_userid( $self->posterid );
-}
-
-sub ownerid {
-    my ($self) = @_;
-    if ($self->type eq 'community') {
-        my $super_maintainers = LJ::load_rel_user_cache($self->journalid, 'S') || LJ::load_rel_user_cache($self->journalid, 'A') || [];
-        return $self->journalid unless scalar(@$super_maintainers);
-        return shift @$super_maintainers;   # appoximation in case of absence the supermaintainer in community
-    } else {
-        return ($self->type eq 'entry' ? $self->posterid : $self->journalid);
-    }
-}
-
-# owner of the promoted object:
-# if entry that poster
-# if community/journal that use journal/community user
-sub owner {
-    my ($self) = @_;
-    return LJ::load_userid( $self->ownerid );
-}
-
-sub promoter {
-    my ($self) = @_;
-    # TODO: remove posterid !!!
-    return LJ::load_userid( $self->promoterid || $self->posterid );
-}
-
-sub object {
-    my ($self) = @_;
-    return ( $self->type eq 'entry' ? LJ::Entry->new( $self->journalid, 'jitemid' => $self->jitemid ) : LJ::load_userid( $self->journalid ));
-}
-
-sub is_object_deleted {
-    my ($self) = @_;
-    return ( $self->type eq 'entry' ? ! $self->object : ! ($self->object && $self->object->is_visible) );
-}
-
-# TODO: Entry specific!!!! Incapsulate
-sub entry {
-    my ($self) = @_;
-    return LJ::Entry->new( $self->journalid, 'jitemid' => $self->jitemid );
-}
-
-# NOTE: Always return object URL,
-# ??? But if journal/community deleted?
-sub object_url {
-    my ($self) = @_;
-    return ( $self->type eq 'entry' ?  $self->journal->journal_base . '/' . $self->ditemid . '.html' :  $self->journal->journal_base );
-}
-
-=item is_promoting
-    Ensure that promotion contains the specified object
-    In fact that is comparison OP
-=cut
-sub is_promoting {
-    my ($self, $object) = @_;
-    return $self->type eq 'entry' ? $object && $object->isa('LJ::Entry') && $self->journalid == $object->...
 (truncated)
Tags: ljcom, pl, pm, sunnyman, tmpl, vtroitsky
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments