summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormark <mark>2010-07-30 22:08:55 +0000
committermark <mark>2010-07-30 22:08:55 +0000
commita95bfdb0174c66cbf8444967efcd04cb638d727c (patch)
tree95ad520041d6dc86e2c4426a969af24838c6ef77
parentb11dd92ff638a29c5b187fe878f05f5323e4337a (diff)
payment receipts use msg_template, RT#9060
-rw-r--r--FS/FS/Conf.pm13
-rw-r--r--FS/FS/cust_pay.pm118
-rw-r--r--FS/FS/msg_template.pm133
-rw-r--r--httemplate/edit/msg_template.html11
4 files changed, 197 insertions, 78 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 92a990d8d..ce8bd296e 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -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' => [
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index c55dd48b7..eee263a1d 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -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
diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm
index 958acef1b..c6622b1db 100644
--- a/FS/FS/msg_template.pm
+++ b/FS/FS/msg_template.pm
@@ -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
diff --git a/httemplate/edit/msg_template.html b/httemplate/edit/msg_template.html
index 01d866dfb..67eae185d 100644
--- a/httemplate/edit/msg_template.html
+++ b/httemplate/edit/msg_template.html
@@ -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',
);