summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2016-05-04 16:22:44 -0700
committerIvan Kohler <ivan@freeside.biz>2016-05-04 16:22:44 -0700
commitbb64a357a453b06e0079444ce21c7e85d9c1527d (patch)
treedef10d4e2f229e6e514968a70e6fed2e23bfee9e /FS
parentf97b6be9496d0948c81052cd060784554c26b58f (diff)
parentff9e2f92752c65f3bc3f1812111591bbd2cb355f (diff)
Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE_3_BRANCH
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/Misc.pm7
-rw-r--r--FS/FS/cust_main.pm109
-rw-r--r--FS/FS/cust_main/Billing_Realtime.pm13
-rw-r--r--FS/FS/cust_main_Mixin.pm23
-rw-r--r--FS/FS/msg_template.pm22
5 files changed, 167 insertions, 7 deletions
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
index e425c4a4b..eedc736ee 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -256,6 +256,13 @@ sub send_email {
push @to, $options{bcc} if defined($options{bcc});
# make sure
my @env_to = split(/\s*,\s*/, join(', ', @to));
+ # strip display-name from envelope addresses
+ foreach (@env_to) {
+ s/^\s*//;
+ s/\s*$//;
+ s/^(.*)\s*<(.*@.*)>$/$2/;
+ }
+
local $@; # just in case
eval { sendmail($message, { transport => $transport,
from => $from,
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 6e666466d..62759be69 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -542,6 +542,16 @@ sub insert {
}
+ # validate card (needs custnum already set)
+ if ( $self->payby =~ /^(CARD|DCRD)$/
+ && $conf->exists('business-onlinepayment-verification') ) {
+ $error = $self->realtime_verify_bop({ 'method'=>'CC' });
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
warn " setting contacts\n"
if $DEBUG > 1;
@@ -1540,6 +1550,20 @@ sub replace {
return $error if $error;
if ( $conf->exists('business-onlinepayment-verification') ) {
+ #need to standardize paydate for this, false laziness with check
+ my( $m, $y );
+ if ( $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/ ) {
+ ( $m, $y ) = ( $1, length($2) == 4 ? $2 : "20$2" );
+ } elsif ( $self->paydate =~ /^19(\d{2})[\/\-](\d{1,2})[\/\-]\d+$/ ) {
+ ( $m, $y ) = ( $2, "19$1" );
+ } elsif ( $self->paydate =~ /^(20)?(\d{2})[\/\-](\d{1,2})[\/\-]\d+$/ ) {
+ ( $m, $y ) = ( $3, "20$2" );
+ } else {
+ return "Illegal expiration date: ". $self->paydate;
+ }
+ $m = sprintf('%02d',$m);
+ $self->paydate("$y-$m-01");
+
$error = $self->realtime_verify_bop({ 'method'=>'CC' });
return $error if $error;
}
@@ -3368,6 +3392,91 @@ sub invoicing_list_emailonly_scalar {
join(', ', $self->invoicing_list_emailonly);
}
+=item contact_list [ CLASSNUM, ... ]
+
+Returns a list of contacts (L<FS::contact> objects) for the customer. If
+a list of contact classnums is given, returns only contacts in those
+classes. If '0' is given, also returns contacts with no class.
+
+If no arguments are given, returns all contacts for the customer.
+
+=cut
+
+sub contact_list {
+ my $self = shift;
+ my $search = {
+ table => 'contact',
+ select => 'contact.*',
+ extra_sql => ' WHERE contact.custnum = '.$self->custnum,
+ };
+
+ my @orwhere;
+ my @classnums;
+ foreach (@_) {
+ if ( $_ eq '0' ) {
+ push @orwhere, 'contact.classnum is null';
+ } elsif ( /^\d+$/ ) {
+ push @classnums, $_;
+ } else {
+ die "bad classnum argument '$_'";
+ }
+ }
+
+ if (@classnums) {
+ push @orwhere, 'contact.classnum IN ('.join(',', @classnums).')';
+ }
+ if (@orwhere) {
+ $search->{extra_sql} .= ' AND (' .
+ join(' OR ', map "( $_ )", @orwhere) .
+ ')';
+ }
+
+ qsearch($search);
+}
+
+=item contact_list_email [ CLASSNUM, ... ]
+
+Same as L</contact_list>, but returns email destinations instead of contact
+objects. Also accepts 'invoice' as an argument, in which case this will also
+return the invoice email address if any.
+
+=cut
+
+sub contact_list_email {
+ my $self = shift;
+ my @classnums;
+ my $and_invoice;
+ foreach (@_) {
+ if (/^invoice$/) {
+ $and_invoice = 1;
+ } else {
+ push @classnums, $_;
+ }
+ }
+ my %emails;
+ # if the only argument passed was 'invoice' then no classnums are
+ # intended, so skip this.
+ if ( @classnums ) {
+ my @contacts = $self->contact_list(@classnums);
+ foreach my $contact (@contacts) {
+ foreach my $contact_email ($contact->contact_email) {
+ # unlike on 4.x, we have a separate list of invoice email
+ # destinations.
+ # make sure they're not redundant with contact emails
+ my $dest = $contact->firstlast . ' <' . $contact_email->emailaddress . '>';
+ $emails{ $contact_email->emailaddress } = $dest;
+ }
+ }
+ }
+ if ( $and_invoice ) {
+ foreach my $email ($self->invoicing_list_emailonly) {
+ my $dest = $self->name_short . ' <' . $email . '>';
+ $emails{ $email } ||= $dest;
+ }
+ }
+ values %emails;
+}
+
=item referral_custnum_cust_main
Returns the customer who referred this customer (or the empty string, if
diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm
index 90fda5eb2..6c0b655a2 100644
--- a/FS/FS/cust_main/Billing_Realtime.pm
+++ b/FS/FS/cust_main/Billing_Realtime.pm
@@ -1938,8 +1938,10 @@ sub realtime_verify_bop {
);
$reverse->content( 'action' => 'Reverse Authorization',
+ $self->_bop_auth(\%options),
# B:OP
+ 'amount' => '1.00',
'authorization' => $transaction->authorization,
'order_number' => $ordernum,
@@ -1969,6 +1971,17 @@ sub realtime_verify_bop {
}
+ } else { # is not success
+
+ # status is 'done' not 'declined', as in _realtime_bop_result
+ $cust_pay_pending->status('done');
+ $cust_pay_pending->statustext( $transaction->error_message || 'Unknown error' );
+ # could also record failure_status here,
+ # but it's not supported by B::OP::vSecureProcessing...
+ # need a B::OP module with (reverse) auth only to test it with
+ my $cpp_declined_err = $cust_pay_pending->replace;
+ return $cpp_declined_err if $cpp_declined_err;
+
}
###
diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm
index 96e520d52..dee9aa831 100644
--- a/FS/FS/cust_main_Mixin.pm
+++ b/FS/FS/cust_main_Mixin.pm
@@ -380,6 +380,12 @@ HTML body
Text body
+=item to_contact_classnum
+
+The customer contact class (or classes, as a comma-separated list) to send
+the message to. If unspecified, will be sent to any contacts that are marked
+as invoice destinations (the equivalent of specifying 'invoice').
+
=back
Returns an error message, or false for success.
@@ -403,6 +409,7 @@ sub email_search_result {
my $subject = delete $param->{subject};
my $html_body = delete $param->{html_body};
my $text_body = delete $param->{text_body};
+ my $to_contact_classnum = delete $param->{to_contact_classnum};
my $error = '';
my $job = delete $param->{'job'}
@@ -455,10 +462,20 @@ sub email_search_result {
%message = $msg_template->prepare(
'cust_main' => $cust_main,
'object' => $obj,
+ 'to_contact_classnum' => $to_contact_classnum,
);
- }
- else {
- my @to = $cust_main->invoicing_list_emailonly;
+
+ } else {
+ # 3.x: false laziness with msg_template.pm; on 4.x, all email notices
+ # are generated from templates and this case goes away
+ my @classes;
+ if ( $to_contact_classnum ) {
+ @classes = ref($to_contact_classnum) ? @$to_contact_classnum : split(',', $to_contact_classnum);
+ }
+ if (!@classes) {
+ @classes = ( 'invoice' );
+ }
+ my @to = $cust_main->contact_list_email(@classes);
next if !@to;
%message = (
diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm
index 70e556924..50a9b3f27 100644
--- a/FS/FS/msg_template.pm
+++ b/FS/FS/msg_template.pm
@@ -383,12 +383,26 @@ sub prepare {
my @to;
if ( exists($opt{'to'}) ) {
+
@to = split(/\s*,\s*/, $opt{'to'});
+
+ } elsif ( $cust_main ) {
+
+ my @classes;
+ if ( $opt{'to_contact_classnum'} ) {
+ my $classnum = $opt{'to_contact_classnum'};
+ @classes = ref($classnum) ? @$classnum : split(',', $classnum);
+ }
+ if (!@classes) {
+ @classes = ( 'invoice' );
+ }
+ @to = $cust_main->contact_list_email(@classes);
+
+ } else {
+
+ die 'no To: address or cust_main object specified';
+
}
- else {
- @to = $cust_main->invoicing_list_emailonly;
- }
- # no warning when preparing with no destination
my $from_addr = $self->from_addr;