ljr/ljcom/bin/maint/pay.pl

714 lines
27 KiB
Perl
Raw Permalink Normal View History

2019-02-05 21:49:12 +00:00
#!/usr/bin/perl
#
use strict;
use vars qw(%maint);
$maint{'pay_mail'} = sub
{
require "$ENV{'LJHOME'}/cgi-bin/paylib.pl";
my $dbh = LJ::get_db_writer();
my $sth;
my $now = time();
# we don't mail receipts (yet?) to non-users paying, or carts w/ no price (eg coppa verifications)
$dbh->do("UPDATE payments SET mailed='X' WHERE mailed='N' AND forwhat='cart' AND (userid=0 OR amount=0)");
$sth = $dbh->prepare("SELECT u.user, u.email, u.name, p.* FROM payments p, user u ".
"WHERE p.userid=u.userid AND p.mailed='N' ".
"AND (IFNULL(p.giveafter,0) = 0 OR $now >= p.giveafter)");
$sth->execute;
die $dbh->errstr if $dbh->err;
while (my $p = $sth->fetchrow_hashref)
{
my $note_msg = sub {
return "" unless $p->{'notes'};
# this will get run through Text::Wrap when it's emailed
my $notes = $p->{'notes'};
$notes =~ s/\n/\n /g;
return "Here are some notes associated with this payment:\n\n" .
" $notes\n\n";
};
if ($p->{'forwhat'} eq "cart") {
my $cart = "$p->{'payid'}-$p->{'anum'}";
LJ::send_mail({
'to' => $p->{'email'},
'from' => $LJ::ACCOUNTS_EMAIL,
'fromname' => $LJ::SITENAMESHORT,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => "Payment received (Order $cart)",
'body' => ("Your payment of \$$p->{'amount'} for order $cart was received and the order is now being processed.\n\n".
"For your reference, you can view the order here:\n\n".
" $LJ::SITEROOT/pay/?c=$cart\n\n".
$note_msg->() .
"We thank you for supporting the site,\n\n".
"$LJ::SITENAMESHORT Team"
)});
$dbh->do("UPDATE payments SET mailed='Y' WHERE payid=$p->{'payid'}");
next;
}
if ($p->{'forwhat'} eq "rename") {
my $token = LJ::Pay::new_rename_token($dbh, $p->{'payid'})
or next;
LJ::send_mail({
'to' => $p->{'email'},
'from' => $LJ::ACCOUNTS_EMAIL,
'fromname' => $LJ::SITENAMESHORT,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => "Rename Token",
'body' => ("Here is the username rename token you purchased:\n\n".
" $token\n\n".
"You can use it here:\n\n".
" $LJ::SITEROOT/rename/use.bml?token=$token\n\n".
"For more information regarding account renames, read:\n\n".
" $LJ::SITEROOT/rename/\n\n".
$note_msg->() .
"$LJ::SITENAMESHORT Team"
),
});
$dbh->do("UPDATE payments SET mailed='Y', used='Y' WHERE payid=$p->{'payid'}");
next;
}
my $howmany = $p->{'months'} == 99 ? "UNLIMITED" : $p->{'months'};
print "$p->{'payid'}: Mailing $p->{'email'} ($howmany) ...\n";
$p->{'notes'} =~ s/\r//g;
my $msg;
$msg .= "$p->{'name'} ...\n\n";
$msg .= "Your $LJ::SITENAMESHORT payment of \$$p->{'amount'} was received $p->{'daterecv'}";
if ($p->{'forwhat'} eq "account") {
$msg .= " and your account has been credited with $howmany more months";
}
$msg .= ".\n\n";
$msg .= $note_msg->();
$msg .= "We thank you for supporting the site,\n\n$LJ::SITENAMESHORT Team";
LJ::send_mail({
'to' => $p->{'email'},
'from' => $LJ::ACCOUNTS_EMAIL,
'fromname' => $LJ::SITENAMESHORT,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => "$LJ::SITENAMESHORT Payment Received -- \#$p->{'payid'}",
'body' => $msg,
});
$dbh->do("UPDATE payments SET mailed='Y' WHERE payid=$p->{'payid'}");
}
};
$maint{'pay_updateaccounts'} = sub
{
require "$ENV{'LJHOME'}/cgi-bin/paylib.pl";
my $dbh = LJ::get_db_writer()
or die "Could not contact global database master";
# for some reason, use of purchased codes doesn't always apply payment
# to account when it's created. some code path involved when paypal
# servers are being lame isn't as robust, or something. in any case,
# this query fixes it:
my $sth = $dbh->prepare
("SELECT ac.rcptid, p.payid ".
"FROM acctcode ac, acctpay ap, payments p ".
"WHERE p.userid=0 AND p.used='N' AND p.payid=ap.payid AND ".
" ap.acid=ac.acid AND ac.rcptid <> 0");
$sth->execute;
while (my ($userid, $payid) = $sth->fetchrow_array) {
$dbh->do("UPDATE payments SET userid=$userid WHERE payid=$payid AND userid=0");
print "Fix payid=$payid to userid=$userid.\n";
}
# and now, back to what this maint task is supposed to do:
my $now = time();
$sth = $dbh->prepare("SELECT payid, userid, months, forwhat, amount, method, datesent ".
"FROM payments WHERE used='N' ".
"AND (IFNULL(giveafter,0) = 0 OR $now >= giveafter)");
$sth->execute;
die $dbh->errstr if $dbh->err;
my @used = ();
while (my $p = $sth->fetchrow_hashref)
{
my $userid = $p->{'userid'};
# check userids of all the affected clusterids before deciding whether to process this payment
my %userids = $userid ? ($userid => 1) : ();
if ($p->{'forwhat'} eq 'cart') {
my $s = $dbh->prepare("SELECT rcptid FROM payitems WHERE payid=? AND rcptid>0");
$s->execute($p->{'payid'});
die $dbh->errstr if $dbh->err;
while (my $uid = $s->fetchrow_array) {
$userids{$uid} = 1;
}
}
if (%userids) {
# call into LJ::load_userids_multi() to get clusterids for these users
# -- cheap because we load all payment userids later during processing
my $users = LJ::load_userids(keys %userids);
# verify we can get all of the handles necessary to complete this request
my $dirty = 0;
foreach (values %$users) {
$dirty = $_->{clusterid}, last unless $_->writer;
}
if ($dirty) {
print "Cluster $dirty unreachable, skipping payment: $p->{payid}\n";
next;
}
}
print "Payment: $p->{'payid'} ($p->{'forwhat'})\n";
# mail notification of large orders... but only if it was automatically processed
if ($LJ::ACCOUNTS_EMAIL && $LJ::LARGE_ORDER_NOTIFY &&
($p->{'method'} eq "cc" || $p->{'method'} eq "paypal") &&
$p->{'amount'} > $LJ::LARGE_ORDER_NOTIFY) {
my $dollars = sub { sprintf("\$%.02f", shift()) };
print "Sending large order notification: " . $dollars->($p->{'amount'}) . "\n";
LJ::send_mail({
'to' => $LJ::ACCOUNTS_EMAIL,
'from' => $LJ::ACCOUNTS_EMAIL,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => "Large order processed: " . $dollars->($p->{'amount'}) .
" [payid: $p->{'payid'}]",
'body' => "This warning has been sent because the following order of over " .
$dollars->($LJ::LARGE_ORDER_NOTIFY) .
" has been processed on $LJ::SITENAMESHORT.\n\n" .
" Amount: " . $dollars->($p->{'amount'}) . "\n" .
" Payid: $p->{'payid'}\n" .
" Method: $p->{'method'}\n" .
" Date Sent: $p->{'datesent'}\n\n"
});
}
# park this payment as used
push @used, $p->{'payid'};
# if a cart, mark each item in the cart as ready to be processed,
# then we'll do that below.
if ($p->{'forwhat'} eq "cart") {
$dbh->do("UPDATE payitems SET status='pend' WHERE ".
"payid=? AND status='cart'", undef, $p->{'payid'});
next;
}
### legacy support from here on.
# needs to be for a user
next unless $userid;
# if permanent account, ignore this legacy (non-cart) payment
my $u = LJ::load_userid($userid);
next if $u->{'caps'} & (1 << $LJ::Pay::capinf{'perm'}->{'bit'});
# if there is an error adding paid months, remove from used list
# so we'll try again later
unless (LJ::Pay::add_paid_months($userid, $p->{'months'})) {
pop @used;
}
}
# @used is only populated in legacy (non-cart) case
if (@used) {
my $usedin = join(", ", @used);
$dbh->do("UPDATE payments SET used='Y' WHERE payid IN ($usedin)");
}
my %pay;
my $get_payment = sub {
my $id = shift;
return $pay{$id} if $pay{$id};
return $pay{$id} =
$dbh->selectrow_hashref("SELECT * FROM payments WHERE payid=?",
undef, $id);
};
# get pending cart items
my %payitems = ( 'paidacct' => [], 'other' => [] );
$sth = $dbh->prepare("SELECT * FROM payitems WHERE status='pend'");
$sth->execute;
while (my $pi = $sth->fetchrow_hashref) {
my $key = $pi->{'item'} eq 'perm' ? 'perm' :
$pi->{'item'} eq 'paidacct' ? 'paidacct' : 'other';
push @{$payitems{$key}}, $pi;
}
my %bonus_failure = ();
# paid accounts are special because they have to apply before bonus features
foreach my $pi (@{$payitems{'perm'}}, @{$payitems{'paidacct'}}, @{$payitems{'other'}}) {
next if $pi->{'giveafter'} > $now; # delayed payment
my $pp = $get_payment->($pi->{'payid'});
my $bu = LJ::load_userid($pp->{'userid'}); # buying user, no force needed
my $email = $pi->{'rcptemail'};
my $ru; # rcpt user
if ($pi->{'rcptid'}) {
$ru = LJ::load_userid($pi->{'rcptid'}, "force");
$email = $ru->{'email'};
}
# optional gift header
my $msg;
if ($bu && $bu->{'userid'} != $pi->{'rcptid'}) {
if ($pi->{'anon'}) {
$msg .= "(the following is an anonymous gift)\n\n"
} else {
$msg .= "(the following is a gift from $LJ::SITENAMESHORT user \"$bu->{'user'}\")\n\n";
}
}
my ($token, $tokenid);
my $close = sub {
$dbh->do("UPDATE payitems SET status='done', token=?, tokenid=? ".
"WHERE piid=? AND status='pend'", undef, $token,
$tokenid, $pi->{'piid'});
};
# paid/perm accounts
if ($pi->{'item'} eq "paidacct" || $pi->{'item'} eq "perm") {
my $isacct = $pi->{'item'} eq "paidacct";
my $has_perm = $ru && $ru->{'caps'} & (1 << $LJ::Pay::capinf{'perm'}->{'bit'});
# send 'em a token
if ($pi->{'rcptid'} == 0 || $has_perm) { # rcpt is an email address, or perm acct
$token = LJ::acct_code_generate($bu ? $bu->{userid} : 0);
my ($acid, $auth) = LJ::acct_code_decode($token);
$dbh->do("INSERT INTO acctpayitem (piid, acid) VALUES (?,?)",
undef, $pi->{'piid'}, $acid);
$tokenid = $acid;
my $what;
if ($isacct) {
$what = "$pi->{'qty'} month(s) of paid account time";
} else {
$what = "a permanent account";
}
$msg .= "The following code will give $what to any $LJ::SITENAMESHORT account:\n\n";
$msg .= " $token\n\n";
$msg .= "To apply it to an existing account, visit:\n\n";
$msg .= " $LJ::SITEROOT/paidaccounts/apply.bml?code=$token\n\n";
$msg .= "To create a new account using it, visit:\n\n";
$msg .= " $LJ::SITEROOT/create.bml?code=$token\n\n";
LJ::send_mail({
'to' => $email,
'from' => $LJ::ACCOUNTS_EMAIL,
'fromname' => $LJ::SITENAMESHORT,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => $isacct ? "Paid account" : "Permanent account",
'body' => $msg,
});
$close->();
# don't need to release lock, no rcptid
next;
}
# just set it up now, and tell them it's done.
# no need to release lock since no $ru anyway
next unless $ru;
my $mo;
$mo = $pi->{'qty'} if $isacct;
$mo = 99 if $pi->{'item'} eq "perm";
my $bonus_ref = [];
# modifying paid account status, need to get a lock on the account,
# try again later if we fail to get a lock
next unless LJ::Pay::get_lock($ru);
my $res = LJ::Pay::add_paid_months($ru->{'userid'}, $mo, $bonus_ref);
# finished modifying account, can unconditionally release lock and finish payitem now
LJ::Pay::release_lock($ru);
# some sort of error occurred, log to payvars and try again later
unless ($res) {
LJ::Pay::payvar_append($pi->{'payid'}, "error",
"[" . LJ::mysql_time() . "] unable to apply: item=$pi->{'item'}, qty=$pi->{'qty'}.");
next;
}
# account changes were successful: close transaction, only need to send email now
$close->();
# finish composing email to send to user
my $bonus_added;
if (@$bonus_ref) {
$bonus_added = "Additionally, the following previously deactivated bonus features\n" .
"have been reactivated so you can use the time remaining on them:\n\n" .
join("\n", map { " - " . LJ::Pay::product_name($_->{'item'}, $_->{'size'}, undef, "short") .
": $_->{'daysleft'} days applied" }
sort { $a->{'item'} cmp $b->{'item'} } @$bonus_ref) .
"\n\n";
}
if ($isacct) {
$msg .= "$mo months of paid account time have been added to your $LJ::SITENAMESHORT account for user \"$ru->{'user'}\".\n\n$bonus_added$LJ::SITENAMESHORT Team";
} else {
$msg .= "Your $LJ::SITENAMESHORT account \"$ru->{'user'}\" has been upgraded to a permanent account.\n\n$bonus_added$LJ::SITENAMESHORT Team";
}
# send notification email
LJ::send_mail({
'to' => $email,
'from' => $LJ::ACCOUNTS_EMAIL,
'fromname' => $LJ::SITENAMESHORT,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => $isacct ? "Paid Account" : "Permanent Account",
'body' => $msg,
});
next;
}
# rename tokens
elsif ($pi->{'item'} eq "rename") {
next unless ($token, $tokenid) = LJ::Pay::new_rename_token($dbh, $pp->{'payid'});
# send email notification
LJ::send_mail({
'to' => $email,
'from' => $LJ::ACCOUNTS_EMAIL,
'fromname' => $LJ::SITENAMESHORT,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => "Rename Token",
'body' => "${msg}$LJ::SITENAMESHORT username rename token:\n\n".
" $token\n\n".
"You can use it here:\n\n".
" $LJ::SITEROOT/rename/use.bml?token=$token\n\n".
"For more information regarding account renames, read:\n\n".
" $LJ::SITEROOT/rename/\n\n".
"$LJ::SITENAMESHORT Team",
});
$close->();
next;
}
# clothing items
elsif ($pi->{'item'} eq "clothes") {
$dbh->do("INSERT IGNORE INTO shipping (payid, status, dateready) VALUES (?, 'needs', NOW())",
undef, $pp->{'payid'}) and $close->();
next;
}
# coupons
elsif ($pi->{'item'} eq "coupon") {
# subitem used to be type-dollaramount, but that was redundant
my ($type) = split('-', $pi->{'subitem'});
# If amt < 0, this item is a previously purchased coupon being applied
# to this cart. So we shouldn't generate a new tokenid for it, especially
# since it will have rcptid=0, so we wouldn't know where to mail it anyway.
if ($type eq 'dollaroff' && $pi->{'amt'} > 0) {
($tokenid, $token) =
LJ::Pay::new_coupon("dollaroff", $pi->{'amt'}, $pi->{'rcptid'}, $pp->{'payid'});
# if there was an error, try again later
next unless $tokenid;
LJ::send_mail({
'to' => $email,
'from' => $LJ::ACCOUNTS_EMAIL,
'fromname' => $LJ::SITENAMESHORT,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => "Coupon Purchase",
'body' => "${msg}$LJ::SITENAMESHORT coupon code:\n\n".
" $token\n\n".
"You can redeem it for \$$pi->{amt} USD in $LJ::SITENAMESHORT merchandise and/or services:\n\n".
"$LJ::SITENAMESHORT store:\n" .
" - $LJ::SITEROOT/store/\n\n" .
"$LJ::SITENAMESHORT services:\n" .
" - $LJ::SITEROOT/pay/\n\n" .
"NOTE: Your coupon is only valid for one use, so be sure that your order's " .
"value is greater than or equal to \$$pi->{amt} USD.\n\n" .
"$LJ::SITENAMESHORT Team",
});
# close, but preserve token info
} else {
($token, $tokenid) = ($pi->{'token'}, $pi->{'tokenid'});
}
$close->();
next;
}
# bonus features
elsif (LJ::Pay::is_bonus($pi)) {
# if a bonus item of this type failed to apply, don't try to apply any more
next if exists $bonus_failure{"$pi->{'payid'}-$pi->{'item'}-$pi->{'subitem'}"};
# get a lock since we're about to modify their account,
# try again later if we can't get a lock
next unless LJ::Pay::get_lock($ru);
# apply the bonus item to the recipient user's account
my $res = LJ::Pay::apply_bonus_item($ru, $pi);
# release lock and close regardless of results of operation
LJ::Pay::release_lock($ru);
# if an error, log to payvars (call above also logged to statushistory) and skip the email
unless ($res) {
LJ::Pay::payvar_append($pi->{'payid'}, "error",
"[" . LJ::mysql_time() . "] unable to apply: item=$pi->{'item'}, size=" .
(split("-", $pi->{'subitem'}))[0] . ", qty=$pi->{'qty'}. invalid cart?");
# if there was a failure, all bonus items of this type were marked
# as failed, so we shouldn't try to process any more of them
$bonus_failure{"$pi->{'payid'}-$pi->{'item'}-$pi->{'subitem'}"}++;
next;
}
# at this point time is applied, just need to send mail. so close.
$close->();
# send notification email to user
my $name = LJ::Pay::product_name($pi);
LJ::send_mail({
'to' => $email,
'from' => $LJ::ACCOUNTS_EMAIL,
'fromname' => $LJ::SITENAMESHORT,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => $name,
'body' => "${msg}Your $LJ::SITENAMESHORT account for user \"$ru->{'user'}\" has been " .
"credited with the following bonus feature:\n\n" .
" - $name\n\n" .
"Your account has been updated so you can use your new feature immediately.\n\n" .
"$LJ::SITENAMESHORT Team"
});
next;
# just close -- shipping, coppa, etc
} else {
$close->();
next;
}
}
};
$maint{'pay_lookupstates'} = sub
{
require "$ENV{'LJHOME'}/cgi-bin/paylib.pl";
require "$ENV{'LJHOME'}/cgi-bin/statslib.pl";
my $get_dbr = sub {
my @roles = ('slow');
push @roles, ('slave', 'master') unless $LJ::STATS_FORCE_SLOW;
return LJ::get_dbh({raw=>1}, @roles)
or die "couldn't connect to database";
};
my $dbr = $get_dbr->();
# see where we got to on our last run
my $min_payid = $dbr->selectrow_array("SELECT value FROM blobcache WHERE bckey='pay_lookupstates_pos'")+0;
my $max_payid = $dbr->selectrow_array("SELECT MAX(payid) FROM payments")+0;
my $to_do = $max_payid - $min_payid;
print " -I- $to_do rows to process... ";
unless ($to_do) {
print "done\n\n";
return;
}
print "\n";
# we'll call into LJ::Stats since it has handy functions
my $blocks = LJ::Stats::num_blocks($to_do);
# get some userprop ids
my $propid = LJ::get_prop("user", "sidx_loc")->{id};
foreach my $block (1..$blocks) {
my ($low, $high) = LJ::Stats::get_block_bounds($block, $min_payid);
print LJ::Stats::block_status_line($block, $blocks);
# make sure our database handles aren't stale
$LJ::DBIRole->clear_req_cache();
$dbr = $get_dbr->()
or die "Couldn't connect to global db reader";
# find all payids that don't have a corresponding paystate row
my $rows = $dbr->selectall_arrayref
("SELECT p.payid, p.userid FROM payments p " .
"LEFT JOIN paystates s ON s.payid=p.payid " .
"WHERE s.payid IS NULL AND p.userid > 0 " .
"AND p.payid BETWEEN $low AND $high");
next unless @$rows; # probably won't happen
my %payids_of_userid = (); # userid => [ payids ]
foreach (@$rows) {
my ($payid, $userid) = @$_;
push @{$payids_of_userid{$userid}}, $payid;
}
my @userids = keys %payids_of_userid;
my $userid_bind = join(",", map { "?" } @userids);
my $st_data = $dbr->selectall_arrayref
("SELECT userid, value FROM userprop " .
"WHERE upropid=? AND userid IN ($userid_bind)",
undef, $propid, @userids);
# save userprop data for setting later
my %state_of_userid = map { $_ => "??" } @userids;
foreach (@$st_data) {
my ($userid, $value) = @$_;
my ($ctry, $st) = LJ::Pay::check_country_state((split("-", $value))[0,1]);
# only care about states of 'US'
$state_of_userid{$userid} = $ctry || '??';
$state_of_userid{$userid} .= "-" . ($st || '??') if $ctry eq 'US';
}
# save results in DB
my @vals = ();
my $bind = "";
while (my ($userid, $state) = each %state_of_userid) {
foreach (@{$payids_of_userid{$userid}}) {
push @vals, $_ => $state;
$bind .= "(?,?),";
}
}
chop $bind;
my $dbh = LJ::get_db_writer();
$dbh->do("REPLACE INTO paystates VALUES $bind", undef, @vals);
die "ERROR: " . $dbh->errstr if $dbh->err;
# now save where we got to for subsequent runs
$dbh->do("REPLACE INTO blobcache (bckey, dateupdate, value) " .
"VALUES ('pay_lookupstates_pos', NOW(), ?)",
undef, $max_payid);
die "ERROR: " . $dbh->errstr if $dbh->err;
}
# we're all done
print " -I- Processed $to_do rows... done\n\n";
};
$maint{'pay_unreserve'} = sub
{
use strict;
require "$ENV{'LJHOME'}/cgi-bin/ljlib.pl";
print "Unreserving inventory...\n";
my $dbh = LJ::get_db_writer()
or die "couldn't get master db handle";
my $sth = $dbh->prepare(qq{
SELECT pi.* FROM payitems pi, payments p
WHERE pi.payid=p.payid
AND pi.qty_res > 0 AND pi.status='cart' AND p.mailed='C'
AND (
(p.method='cc' and p.datesent < DATE_SUB(NOW(), INTERVAL 3 DAY))
OR
(p.datesent < DATE_SUB(NOW(), INTERVAL 12 DAY))
)
});
die $dbh->errstr if $dbh->err;
$sth->execute;
while (my $it = $sth->fetchrow_hashref) {
print "$it->{'piid'}: $it->{'item'} $it->{'subitem'} $it->{'qty_res'}\n";
$dbh->do("UPDATE inventory SET avail=avail+? WHERE item=? AND subitem=?",
undef, $it->{'qty_res'}, $it->{'item'}, $it->{'subitem'});
die $dbh->errstr if $dbh->err;
$dbh->do("UPDATE payitems SET qty_res=0 WHERE piid=?", undef, $it->{'piid'});
die $dbh->errstr if $dbh->err;
}
};
$maint{'pay_shipping_notify'} = sub
{
use strict;
require "$ENV{'LJHOME'}/cgi-bin/ljlib.pl";
die "no shipping email"
unless $LJ::SHIPPING_EMAIL;
die "no shipping contact email"
unless $LJ::SHIPPING_CONTACT_EMAIL;
my $dbh = LJ::get_db_writer()
or die "couldn't get master db handle";
my ($ct, $min_date) =
$dbh->selectrow_array("SELECT COUNT(*), MIN(dateready) " .
"FROM shipping WHERE status='needs'");
LJ::send_mail({
'to' => $LJ::SHIPPING_EMAIL,
'from' => $LJ::ADMIN_EMAIL,
'fromname' => $LJ::SITENAME,
'wrap' => 1,
'charset' => 'utf-8',
'subject' => "$ct Outstanding $LJ::SITENAME Merchandise Orders",
'body' =>
"There are currently $ct outstanding $LJ::SITENAME merchandise orders in need of shipping. " .
"The oldest of which became ready at $min_date.\n\n" .
"Visit the following URL for details about currently outstanding orders. Please print all " .
"invoices and include a copy of each order's invoice with its shipment, which should be " .
"the cheaper of UPS Ground or FedEx Ground.\n\n" .
" $LJ::SITEROOT/admin/accounts/shipping_labels.bml\n\n" .
"As orders are shipped, please enter their order numbers at the following URL so that " .
"$LJ::SITENAME\'s cart system will be able to stop selling merchandise as supplies run out.\n\n" .
" $LJ::SITEROOT/admin/accounts/shipping_finish.bml\n\n" .
"Please contact $LJ::SHIPPING_CONTACT_EMAIL directly with any questions or problems.\n\n" .
"Regards,\n" .
"$LJ::SITENAME Team\n",
});
print " -I- Emailed $LJ::SHIPPING_EMAIL\n\n";
};
1;