diff --git a/docs/gotcha.txt b/docs/gotcha.txt index 62e95646e..1591c5be9 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -7,6 +7,11 @@ upgrading from one version to the next, or even between multiple versions. Be sure to heed the warnings contained herein as they will save you many hours of grief. +7.8.2 +-------------------------------------------------------------------- + * WebGUI now requires Business::PayPal::API 0.62 or higher. + + 7.8.1 -------------------------------------------------------------------- @@ -32,6 +37,7 @@ save you many hours of grief. prefix from the filename. + 7.8.0 -------------------------------------------------------------------- diff --git a/docs/upgrades/upgrade_7.8.1-7.8.2.pl b/docs/upgrades/upgrade_7.8.1-7.8.2.pl index ccb57d8c6..41a31276e 100644 --- a/docs/upgrades/upgrade_7.8.1-7.8.2.pl +++ b/docs/upgrades/upgrade_7.8.1-7.8.2.pl @@ -35,18 +35,36 @@ fixTableDefaultCharsets($session); correctWikiAttachmentPermissions( $session ); transactionsNotifications( $session ); fixBadVarCharColumns ( $session ); +addVendorPayouts($session); finish($session); # this line required #---------------------------------------------------------------------------- -# Describe what our function does -#sub exampleFunction { -# my $session = shift; -# print "\tWe're doing some stuff here that you should know about... " unless $quiet; -# # and here's our code -# print "DONE!\n" unless $quiet; -#} +sub addVendorPayouts { + my $session = shift; + print "\tAdding vendor payouts... " unless $quiet; + my $db = $session->db; + $db->write(" create table if not exists vendorPayoutLog ( + payoutId char(22) binary not null primary key, + isSuccessful tinyint(1) not null, + errorCode char(10), + errorMessage char(255), + paypalTimestamp char(20) not null, + amount decimal(7,2) not null, + currency char(3) not null, + correlationId char(13) not null, + paymentInformation char(255) not null + )"); + $db->write(" create table if not exists vendorPayoutLog_items ( + payoutId char(22) binary not null, + transactionItemId char(22) binary not null, + amount decimal(7,2) not null, + primary key( payoutId, transactionItemId ) + )"); + + print "DONE!\n" unless $quiet; +} #---------------------------------------------------------------------------- sub fixTableDefaultCharsets { diff --git a/lib/WebGUI/Workflow/Activity/PayoutVendors.pm b/lib/WebGUI/Workflow/Activity/PayoutVendors.pm new file mode 100644 index 000000000..386abf873 --- /dev/null +++ b/lib/WebGUI/Workflow/Activity/PayoutVendors.pm @@ -0,0 +1,262 @@ +package WebGUI::Workflow::Activity::PayoutVendors; + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2009 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; + +use Business::PayPal::API qw{ MassPay }; +use Data::Dumper; +use WebGUI::Mail::Send; + +use base 'WebGUI::Workflow::Activity'; + +=head1 NAME + +Package WebGUI::Workflow::Activity::PayoutVendors + +=head1 DESCRIPTION + +Pays profits to vendors, currently via paypal, but others may be added in the future. + +=head1 SYNOPSIS + +See WebGUI::Workflow::Activity for details on how to use any activity. + +=head1 METHODS + +These methods are available from this class: + +=cut + +#------------------------------------------------------------------- + +=head2 definition () + +See WebGUI::Workflow::Activity for details. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = WebGUI::International->new($session, "Workflow_Activity_PayoutVendors"); + + tie my %properties, 'Tie::IxHash', ( + paypalUsername => { + fieldType => 'text', + label => $i18n->get('PayPal username'), + }, + paypalPassword => { + fieldType => 'password', + label => $i18n->get('PayPal password'), + }, + paypalSignature => { + fieldType => 'text', + label => $i18n->get('PayPal signature'), + }, + useSandbox => { + fieldType => 'yesNo', + label => $i18n->get('Use in Sandbox (test-mode)'), + defaultValue => 0, + }, + currencyCode => { + fieldType => 'text', + label => $i18n->get('Currency code'), + maxlength => 3, + size => 3, + defaultValue => 'USD', + }, + paypalSubject => { + fieldType => 'text', + label => $i18n->get('Subject for vendor notication email'), + defaultValue => $i18n->get('Vendor payout from').' ' . $session->setting->get('companyUrl'), + }, + notificationGroupId => { + fieldType => 'group', + label => $i18n->get('Notify on error'), + }, + ); + + push @{ $definition }, { + name => $i18n->get('Vendor Payout'), + properties => \%properties, + }; + + return $class->SUPER::definition( $session, $definition ); +} + + +#------------------------------------------------------------------- + +=head2 payoutVendor (vendorId) + +Sends unsent vendor payouts to paypal. + +=head3 vendorId + +The vendor to be sent his payouts. + +=cut + +sub payoutVendor { + my $self = shift; + my $vendorId = shift; + my $db = $self->session->db; + my $payoutId = $self->session->id->generate; + + # Instanciate vendor and check if he exists. + my $vendor = WebGUI::Shop::Vendor->new( $self->session, $vendorId ); + unless ( $vendor ) { + $self->session->log->error( "Could not instanciate vendor with id [$vendorId] for payout" ); + return undef; + } + + # check to see that the vendor has a payout address + if ($vendor->get('paymentInformation') eq '') { + $self->session->log->warn("Vendor ".$vendor->getId." hasn't specified a payout address."); + return undef; + } + + # Fetch all transactionItems that are scheduled for payout to the vendor. + my $sth = $db->read( + 'select itemId, vendorPayoutAmount from transactionItem ' + . ' where vendorId=? and vendorPayoutStatus=? and vendorPayoutAmount > 0', + [ + $vendorId, + 'Scheduled', + ] + ); + + # Process all transaction items and log them in the db. + my $totalAmount = 0; + while ( my $item = $sth->hashRef ) { + $totalAmount += $item->{ vendorPayoutAmount }; + + $db->write( 'insert into vendorPayoutLog_items (payoutId, transactionItemId, amount) values (?,?,?)', [ + $payoutId, + $item->{ itemId }, + $item->{ vendorPayoutAmount }, + ] ); + } + my $itemCount = $sth->rows; + $sth->finish; + + # Do PayPal MassPay request + my $pp = new Business::PayPal::API( + Username => $self->get('paypalUsername'), + Password => $self->get('paypalPassword'), + Signature => $self->get('paypalSignature'), + sandbox => $self->get('useSandbox'), + ); + my %response = $pp->MassPay( + EmailSubject => $self->get('paypalSubject'), + currencyID => $self->get('currencyCode'), + MassPayItems => [ { + ReceiverEmail => $vendor->get('paymentInformation'), + Amount => $totalAmount, + UniqueID => $payoutId, + Note => "Payout for $itemCount sold items", + } ], + ); + + # Process paypal response + my $payoutDetails = { + payoutId => $payoutId, + isSuccessful => $response{ Ack } eq 'Success' ? 1 : 0, + paypalTimestamp => $response{ Timestamp }, + correlationId => $response{ CorrelationID }, + amount => $totalAmount, + currency => $self->get('currencyCode'), + paymentInformation => $vendor->get('paymentInformation'), + }; + if ( $response{ Ack } ne 'Success' ) { + # An error occurred, keep the error codes + my $errorCode = $response{ Error }->[ 0 ]->{ ErrorCode }; + my $errorMessage = $response{ Error }->[ 0 ]->{ LongMessage }; + + # TODO: Send out email. + my $mail = WebGUI::Mail::Send->create($self->session, { + toGroup => $self->get('notificationGroupId'), + subject => 'Vendor payout error', + }); + $mail->addText( + "An error occurred during an automated vendor payout attempt. Response details:\n" + . Dumper( \%response ) + . "\n\nVendor information:\n" + . Dumper( $vendor->get ) + ); + $mail->send; + + $payoutDetails->{ errorCode } = $errorCode; + $payoutDetails->{ errorMessage } = $errorMessage; + } + else { + # The transaction was successful, so change the state of the transactionItems to Paid. + $db->write( + 'update transactionItem set vendorPayoutStatus=? where itemId in ( ' + .' select transactionItemId from vendorPayoutLog_items where payoutId=? ' + .')', + [ + 'Paid', + $payoutId, + ] + ); + } + + # Persist response data to db + $db->setRow( 'vendorPayoutLog', 'payoutId', $payoutDetails, $payoutId ); + +}; + + +#------------------------------------------------------------------- + +=head2 execute () + +See WebGUI::Workflow::Activity for details. + +=cut + +sub execute { + my $self = shift; + my $object = shift; + my $instance = shift; + my $start = time; + my $ttl = $self->getTTL; + + # Fetch vendors eligible for payout. + my $sth = $self->session->db->read( + "select distinct vendorId from transactionItem where vendorPayoutStatus='Scheduled' and vendorPayoutAmount > 0" + ); + + # Pay on a vendor by vendor basis. + while ( (my $vendorId) = $sth->array ) { + $self->payoutVendor( $vendorId ); + + # Make sure we won't run longer than allowed. + if ( ( time - $start + 1 ) >= $ttl ) { + $sth->finish; + return $self->WAITING( 1 ); + } + } + + $sth->finish; + + return $self->COMPLETE; +} + +1; + diff --git a/lib/WebGUI/i18n/English/Workflow_Activity_PayoutVendors.pm b/lib/WebGUI/i18n/English/Workflow_Activity_PayoutVendors.pm new file mode 100644 index 000000000..facf0c908 --- /dev/null +++ b/lib/WebGUI/i18n/English/Workflow_Activity_PayoutVendors.pm @@ -0,0 +1,63 @@ +package WebGUI::i18n::English::Workflow_Activity_PayoutVendors; + +use strict; + +our $I18N = { + 'PayPal username' => { + message => q|PayPal username|, + lastUpdated => 0, + context => q|field label| + }, + + 'PayPal password' => { + message => q|PayPal password|, + lastUpdated => 0, + context => q|field label| + }, + + 'PayPal signature' => { + message => q|PayPal signature|, + lastUpdated => 0, + context => q|field label| + }, + + 'Use in Sandbox (test-mode)' => { + message => q|Use in Sandbox (test-mode)|, + lastUpdated => 0, + context => q|field label| + }, + + 'Currency code' => { + message => q|Currency code|, + lastUpdated => 0, + context => q|field label| + }, + + 'Subject for vendor notification email' => { + message => q|Subject for vendor notification email|, + lastUpdated => 0, + context => q|field label| + }, + + 'Vendor payout from' => { + message => q|Vendor payout from|, + lastUpdated => 0, + context => q|Prefix that goes before company URL to create the default value from Subject for vendor notification email.| + }, + + 'Notify on error' => { + message => q|Notify on error|, + lastUpdated => 0, + context => q|field label| + }, + + 'Vendor Payout' => { + message => q|Vendor Payout|, + lastUpdated => 0, + context => q|field label| + }, + +}; + +1; +#vim:ft=perl diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index 90d0aa07a..c5b468305 100755 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -133,6 +133,7 @@ checkModule('Digest::SHA', '5.47' ); checkModule("CSS::Minifier::XS", "0.03" ); checkModule("JavaScript::Minifier::XS", "0.05" ); checkModule("Readonly", "1.03" ); +checkModule("Business::PayPal::API", "0.62" ); failAndExit("Required modules are missing, running no more checks.") if $missingModule;