},
{
+ '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' => [
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
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
;
return $error if $error;
- my $body = $self->body;
- $body =~ s/ / /g; # just in case these somehow get in
- $self->body($body);
-
$self->mime_type('text/html') unless $self->mime_type;
$self->SUPER::check;
=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
}
}
}
- $_ = 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
+ $_
+ } @$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;
'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) ),
);
}
=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(@_)));
# 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.
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
[ 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(
],
'cust_bill' => [qw(
invnum
+ _date
)],
#XXX not really thinking about cust_bill substitutions quite yet
),
[ 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)
+ } ],
+ ],
};
}
[ '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' ],
);
}
}
+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
'$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',
],
'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'} };
'cust_main' => 'Customer status and payment info',
'cust_pkg' => 'Package fields',
'cust_bill' => 'Invoice fields',
+'cust_pay' => 'Payment fields',
'svc_acct' => 'Login service fields',
);