payment receipts use msg_template, RT#9060
authormark <mark>
Fri, 30 Jul 2010 22:08:55 +0000 (22:08 +0000)
committermark <mark>
Fri, 30 Jul 2010 22:08:55 +0000 (22:08 +0000)
FS/FS/Conf.pm
FS/FS/cust_pay.pm
FS/FS/msg_template.pm
httemplate/edit/msg_template.html

index 92a990d..ce8bd29 100644 (file)
@@ -1241,15 +1241,22 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'payment_receipt_msgnum',
+    'section'     => 'notification',
+    'description' => 'Template to use for payment receipts.',
+    %msg_template_options,
+  },
+
+  {
     'key'         => 'payment_receipt_email',
-    'section'     => 'billing',
-    'description' => 'Template file for payment receipts.  Payment receipts are sent to the customer email invoice destination(s) when a payment is received.  See the <a href="http://search.cpan.org/dist/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language.  The following variables are available: <ul><li><code>$date</code> <li><code>$name</code> <li><code>$paynum</code> - Freeside payment number <li><code>$paid</code> - Amount of payment <li><code>$payby</code> - Payment type (Card, Check, Electronic check, etc.) <li><code>$payinfo</code> - Masked credit card number or check number <li><code>$balance</code> - New balance<li><code>$pkg</code> - Package (requires payment_receipt-trigger set to "when payment is applied".)</ul>',
+    'section'     => 'deprecated',
+    'description' => 'Template file for payment receipts.  Payment receipts are sent to the customer email invoice destination(s) when a payment is received.',
     'type'        => [qw( checkbox textarea )],
   },
 
   {
     'key'         => 'payment_receipt-trigger',
-    'section'     => 'billing',
+    'section'     => 'notification',
     'description' => 'When payment receipts are triggered.  Defaults to when payment is made.',
     'type'        => 'select',
     'select_hash' => [
index c55dd48..eee263a 100644 (file)
@@ -446,76 +446,82 @@ sub send_receipt {
 
   my $conf = new FS::Conf;
 
-  return ''
-    unless $conf->exists('payment_receipt_email')
-        && grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list;
+  my @invoicing_list = $cust_main->invoicing_list_emailonly;
+  return '' unless @invoicing_list;
 
   $cust_bill ||= ($cust_main->cust_bill)[-1]; #rather inefficient though?
 
   if (    ( exists($opt->{'manual'}) && $opt->{'manual'} )
-       || ! $conf->exists('invoice_html_statement')
+       || ! $conf->exists('invoice_html_statement') # XXX msg_template
        || ! $cust_bill
      ) {
 
-    my $receipt_template = new Text::Template (
-      TYPE   => 'ARRAY',
-      SOURCE => [ map "$_\n", $conf->config('payment_receipt_email') ],
-    ) or do {
-      warn "can't create payment receipt template: $Text::Template::ERROR";
-      return '';
-    };
-
-    my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ }
-                           $cust_main->invoicing_list;
-
-    my $payby = $self->payby;
-    my $payinfo = $self->payinfo;
-    $payby =~ s/^BILL$/Check/ if $payinfo;
-    if ( $payby eq 'CARD' || $payby eq 'CHEK' ) {
-      $payinfo = $self->paymask
-    } else {
-      $payinfo = $self->decrypt($payinfo);
-    }
-    $payby =~ s/^CHEK$/Electronic check/;
-
-    my %fill_in = (
-      'date'         => time2str("%a %B %o, %Y", $self->_date),
-      'name'         => $cust_main->name,
-      'paynum'       => $self->paynum,
-      'paid'         => sprintf("%.2f", $self->paid),
-      'payby'        => ucfirst(lc($payby)),
-      'payinfo'      => $payinfo,
-      'balance'      => $cust_main->balance,
-      'company_name' => $conf->config('company_name', $cust_main->agentnum),
-    );
+    my $error = '';
 
-    if ( $opt->{'cust_pkg'} ) {
-      $fill_in{'pkg'} = $opt->{'cust_pkg'}->part_pkg->pkg;
-      #setup date, other things?
+    if( $conf->exists('payment_receipt_msgnum') ) {
+      my $msg_template = 
+          FS::msg_template->by_key($conf->config('payment_receipt_msgnum'));
+      $error = $msg_template->send('cust_main'=> $cust_main, 'object'=> $self);
     }
+    elsif ( $conf->exists('payment_receipt_email') ) {
+      my $receipt_template = new Text::Template (
+        TYPE   => 'ARRAY',
+        SOURCE => [ map "$_\n", $conf->config('payment_receipt_email') ],
+      ) or do {
+        warn "can't create payment receipt template: $Text::Template::ERROR";
+        return '';
+      };
 
-    send_email(
-      'from'    => $conf->config('invoice_from', $cust_main->agentnum),
-                                 #invoice_from??? well as good as any
-      'to'      => \@invoicing_list,
-      'subject' => 'Payment receipt',
-      'body'    => [ $receipt_template->fill_in( HASH => \%fill_in ) ],
-    );
-
-  } else {
+      my $payby = $self->payby;
+      my $payinfo = $self->payinfo;
+      $payby =~ s/^BILL$/Check/ if $payinfo;
+      if ( $payby eq 'CARD' || $payby eq 'CHEK' ) {
+        $payinfo = $self->paymask
+      } else {
+        $payinfo = $self->decrypt($payinfo);
+      }
+      $payby =~ s/^CHEK$/Electronic check/;
+
+      my %fill_in = (
+        'date'         => time2str("%a %B %o, %Y", $self->_date),
+        'name'         => $cust_main->name,
+        'paynum'       => $self->paynum,
+        'paid'         => sprintf("%.2f", $self->paid),
+        'payby'        => ucfirst(lc($payby)),
+        'payinfo'      => $payinfo,
+        'balance'      => $cust_main->balance,
+        'company_name' => $conf->config('company_name', $cust_main->agentnum),
+      );
+
+      if ( $opt->{'cust_pkg'} ) {
+        $fill_in{'pkg'} = $opt->{'cust_pkg'}->part_pkg->pkg;
+        #setup date, other things?
+      }
 
-    my $queue = new FS::queue {
-       'paynum' => $self->paynum,
-       'job'    => 'FS::cust_bill::queueable_email',
-    };
+      $error = send_email(
+        'from'    => $conf->config('invoice_from', $cust_main->agentnum),
+                                   #invoice_from??? well as good as any
+        'to'      => \@invoicing_list,
+        'subject' => 'Payment receipt',
+        'body'    => [ $receipt_template->fill_in( HASH => \%fill_in ) ],
+      );
 
-    $queue->insert(
-      'invnum'   => $cust_bill->invnum,
-      'template' => 'statement',
-    );
+    } 
+    else { # no payment_receipt_msgnum or payment_receipt_email
 
-  }
+      my $queue = new FS::queue {
+         'paynum' => $self->paynum,
+         'job'    => 'FS::cust_bill::queueable_email',
+      };
 
+      $queue->insert(
+        'invnum'   => $cust_bill->invnum,
+        'template' => 'statement',
+      );
+    }
+  
+    warn "send_receipt: $error\n" if $error;
+  } #$opt{manual} || no invoice_html_statement || customer has no invoices
 }
 
 =item cust_bill_pay
index 958acef..c6622b1 100644 (file)
@@ -8,10 +8,12 @@ use FS::Conf;
 use FS::Record qw( qsearch qsearchs );
 
 use Date::Format qw( time2str );
-use HTML::Entities qw( encode_entities) ;
+use HTML::Entities qw( decode_entities encode_entities ) ;
+use HTML::FormatText;
+use HTML::TreeBuilder;
 use vars '$DEBUG';
 
-$DEBUG=1;
+$DEBUG=0;
 
 =head1 NAME
 
@@ -143,10 +145,6 @@ sub check {
   ;
   return $error if $error;
 
-  my $body = $self->body;
-  $body =~ s/&nbsp;/ /g; # just in case these somehow get in
-  $self->body($body);
-
   $self->mime_type('text/html') unless $self->mime_type;
 
   $self->SUPER::check;
@@ -167,8 +165,8 @@ Customer object (required).
 
 =item object
 
-Additional context object (currently, can be a cust_main object, cust_pkg
-object, or cust_bill object).
+Additional context object (currently, can be a cust_main, cust_pkg, 
+cust_bill, svc_acct, or cust_pay object).
 
 =back
 
@@ -204,30 +202,55 @@ sub prepare {
       } 
     } 
   } 
-  $_ = encode_entities($_) foreach values(%hash); # HTML escape
+  $_ = encode_entities($_) foreach values(%hash);
+
 
   ###
-  # fill-in
+  # clean up template
   ###
-
   my $subject_tmpl = new Text::Template (
     TYPE   => 'STRING',
     SOURCE => $self->subject,
   );
   my $subject = $subject_tmpl->fill_in( HASH => \%hash );
 
+  my $body = $self->body;
+  my ($skin, $guts) = eviscerate($body);
+  @$guts = map { 
+    $_ = decode_entities($_); # turn all punctuation back into itself
+    s/\r//gs;           # remove \r's
+    s/<br[^>]*>/\n/gsi; # and <br /> tags
+    s/<p>/\n/gsi;       # and <p>
+    s/<\/p>//gsi;       # and </p>
+    s/\240/ /gs;        # and &nbsp;
+    $_
+  } @$guts;
+  
+  $body = '';
+  while(@$skin || @$guts) {
+    $body .= shift(@$skin) || '';
+    $body .= shift(@$guts) || '';
+  }
+
+  ###
+  # fill-in
+  ###
+
   my $body_tmpl = new Text::Template (
-    TYPE   => 'STRING',
-    SOURCE => $self->body,
+    TYPE          => 'STRING',
+    SOURCE        => $body,
   );
-  my $body = $body_tmpl->fill_in( HASH => \%hash );
+
+  $body = $body_tmpl->fill_in( HASH => \%hash );
 
   ###
   # and email
   ###
 
   my @to = $cust_main->invoicing_list_emailonly;
-  #unless (@to) { #XXX do something }
+  warn "prepared msg_template with no email destination (custnum ".
+    $cust_main->custnum.")\n"
+    if !@to;
 
   my $conf = new FS::Conf;
 
@@ -237,8 +260,8 @@ sub prepare {
     'to'   => \@to,
     'subject'   => $subject,
     'html_body' => $body,
-    #XXX auto-make a text copy w/HTML::FormatText?
-    #  alas, us luddite mutt/pine users just aren't that big a deal
+    'text_body' => HTML::FormatText->new(leftmargin => 0, rightmargin => 70
+                    )->format( HTML::TreeBuilder->new_from_content($body) ),
   );
 
 }
@@ -250,6 +273,8 @@ Fills in the template and sends it to the customer.  Options are as for
 
 =cut
 
+# broken out from prepare() in case we want to queue the sending,
+# preview it, etc.
 sub send {
   my $self = shift;
   send_email(generate_email($self->prepare(@_)));
@@ -258,6 +283,9 @@ sub send {
 # helper sub for package dates
 my $ymd = sub { $_[0] ? time2str('%Y-%m-%d', $_[0]) : '' };
 
+# needed for some things
+my $conf = new FS::Conf;
+
 #return contexts and fill-in values
 # If you add anything, be sure to add a description in 
 # httemplate/edit/msg_template.html.
@@ -278,7 +306,7 @@ sub substitutions {
       ship_country
       ship_daytime ship_night ship_fax
 
-      payby paymask payname paytype payip
+      paymask payname paytype payip
       num_cancelled_pkgs num_ncancelled_pkgs num_pkgs
       classname categoryname
       balance
@@ -292,6 +320,10 @@ sub substitutions {
       [ paydate_my        => sub { sprintf('%02d/%04d', shift->paydate_monthyear) } ],
       [ otaker_first      => sub { shift->access_user->first } ],
       [ otaker_last       => sub { shift->access_user->last } ],
+      [ payby             => sub { FS::payby->shortname(shift->payby) } ],
+      [ company_name      => sub { 
+          $conf->config('company_name', shift->agentnum) 
+        } ],
     ],
     # next_bill_date
     'cust_pkg'  => [qw( 
@@ -315,6 +347,7 @@ sub substitutions {
     ],
     'cust_bill' => [qw(
       invnum
+      _date
     )],
     #XXX not really thinking about cust_bill substitutions quite yet
     
@@ -323,6 +356,20 @@ sub substitutions {
       ),
       [ password          => sub { shift->getfield('_password') } ],
     ], # for welcome messages
+    'cust_pay' => [qw(
+      paynum
+      _date
+      ),
+      [ paid              => sub { sprintf("%.2f", shift->paid) } ],
+      # overrides the one in cust_main in cases where a cust_pay is passed
+      [ payby             => sub { FS::payby->shortname(shift->payby) } ],
+      [ date              => sub { time2str("%a %B %o, %Y", shift->_date) } ],
+      [ payinfo           => sub { 
+          my $cust_pay = shift;
+          ($cust_pay->payby eq 'CARD' || $cust_pay->payby eq 'CHEK') ?
+            $cust_pay->paymask : $cust_pay->decrypt($cust_pay->payinfo)
+        } ],
+    ],
   };
 }
 
@@ -334,6 +381,7 @@ sub _upgrade_data {
     [ 'cancel_msgnum',   'cancelmessage',      'cancelsubject',  '' ],
     [ 'decline_msgnum',  'declinetemplate',    '',               '' ],
     [ 'impending_recur_msgnum', 'impending_recur_template', '',  '' ],
+    [ 'payment_receipt_msgnum', 'payment_receipt_email', '',     '' ],
     [ 'welcome_msgnum',  'welcome_email',      'welcome_email-subject', 'welcome_email-from' ],
     [ 'warning_msgnum',  'warning_email',      'warning_email-subject', 'warning_email-from' ],
   );
@@ -364,6 +412,55 @@ sub _upgrade_data {
   }
 }
 
+sub eviscerate {
+  # Every bit as pleasant as it sounds.
+  #
+  # We do this because Text::Template::Preprocess doesn't
+  # actually work.  It runs the entire template through 
+  # the preprocessor, instead of the code segments.  Which 
+  # is a shame, because Text::Template already contains
+  # the code to do this operation.
+  my $body = shift;
+  my (@outside, @inside);
+  my $depth = 0;
+  my $chunk = '';
+  while($body || $chunk) {
+    # put all leading non-delimiters into $first
+    my ($first, $rest) =
+        ($body =~ /^((?:\\[{}]|[^{}])*)(.*)$/s);
+    $chunk .= $first;
+    # put a leading delimiter into $delim if there is one
+    my ($delim, $rest) =
+      ($rest =~ /^([{}]?)(.*)$/s);
+
+    if( $delim eq '{' ) {
+      $chunk .= '{';
+      if( $depth == 0 ) {
+        push @outside, $chunk;
+        $chunk = '';
+      }
+      $depth++;
+    }
+    elsif( $delim eq '}' ) {
+      $depth--;
+      if( $depth == 0 ) {
+        push @inside, $chunk;
+        $chunk = '';
+      }
+      $chunk .= '}';
+    }
+    else {
+      # no more delimiters
+      if( $depth == 0 ) {
+        push @outside, $chunk . $rest;
+      } # else ? something wrong
+      last;
+    }
+    $body = $rest;
+  }
+  (\@outside, \@inside);
+}
+
 =back
 
 =head1 BUGS
index 01d866d..67eae18 100644 (file)
@@ -50,6 +50,7 @@ my %substitutions = (
     '$cust_status'    => 'Status',
     '$ucfirst_cust_status'  => 'Status, capitalized',
     '$cust_statuscolor'     => 'Status color code',
+    '$company_name'   => 'Our company name',
   ],
   'contact' => [ # duplicate this for shipping
     '$name'           => 'Company and contact name',
@@ -92,7 +93,14 @@ my %substitutions = (
   ],
   'svc_acct'  => [
     '$username'       => 'Login name',
-    '$password'      => 'Password',
+    '$password'       => 'Password',
+  ],
+  'cust_pay'  => [
+    '$paynum'         => 'Payment#',
+    '$paid'           => 'Amount',
+    '$payby'          => 'Payment method',
+    '$date'           => 'Payment date',
+    '$payinfo'        => 'Card/account# (masked)',
   ],
 );
 my @c = @{ $substitutions{'contact'} };
@@ -107,6 +115,7 @@ tie my %sections, 'Tie::IxHash', (
 'cust_main' => 'Customer status and payment info',
 'cust_pkg'  => 'Package fields',
 'cust_bill' => 'Invoice fields',
+'cust_pay'  => 'Payment fields',
 'svc_acct'  => 'Login service fields',
 );