add option to limit automatic unsuspensions to a specific suspension reason type...
[freeside.git] / FS / FS / TaxEngine / suretax.pm
index 327a728..356f5f3 100644 (file)
@@ -4,7 +4,7 @@ use strict;
 use base 'FS::TaxEngine';
 use FS::Conf;
 use FS::Record qw(qsearch qsearchs dbh);
-use JSON;
+use Cpanel::JSON::XS;
 use XML::Simple qw(XMLin);
 use LWP::UserAgent;
 use HTTP::Request::Common;
@@ -14,15 +14,12 @@ our $DEBUG = 1; # prints progress messages
 #   $DEBUG = 2; # prints decoded request and response (noisy, be careful)
 #   $DEBUG = 3; # prints raw response from the API, ridiculously unreadable
 
-our $json = JSON->new->pretty(1);
+our $json = Cpanel::JSON::XS->new->pretty(0)->shrink(1);
 
 our %taxproduct_cache;
 
 our $conf;
 
-our $host = 'testapi.taxrating.net';
-# production: 'api.taxrating.net'
-
 FS::UID->install_callback( sub {
     $conf = FS::Conf->new;
     # should we enable conf caching here?
@@ -80,7 +77,7 @@ sub build_request {
   ($self->{bill_zip}, $self->{bill_plus4}) =
     split('-', $cust_main->bill_location->zip);
 
-  $self->{regcode} = $REGCODE{ $conf->config('suretax-regulatory_code') };
+  $self->{regcode} = $REGCODE{ $conf->config('suretax-regulatory_code', $agentnum) };
 
   %taxproduct_cache = ();
 
@@ -88,6 +85,8 @@ sub build_request {
   my @lines = map { $self->build_item($_) }
               $cust_bill->cust_bill_pkg;
 
+  return if !@lines;
+
   my $ClientNumber = $conf->config('suretax-client_number')
     or die "suretax-client_number config required.\n";
   my $ValidationKey = $conf->config('suretax-validation_key')
@@ -98,8 +97,8 @@ sub build_request {
     ClientNumber  => $ClientNumber,
     ValidationKey => $ValidationKey,
     BusinessUnit  => $BusinessUnit,
-    DataYear      => '2015', #$date->year,
-    DataMonth     => '04', #sprintf('%02d', $date->month),
+    DataYear      => $date->year,
+    DataMonth     => sprintf('%02d', $date->month),
     TotalRevenue  => sprintf('%.4f', $cust_bill->charged),
     ReturnFileCode    => ($self->{estimate} ? 'Q' : '0'),
     ClientTracking  => $cust_bill->invnum,
@@ -138,8 +137,17 @@ sub build_item {
   my @items;
   my $recur_without_usage = $cust_bill_pkg->recur;
 
+  # use the _configured_ tax location as 'Zipcode' (respecting 
+  # tax-ship_address and tax-pkg_address configs)
   my $location = $cust_bill_pkg->tax_location;
-  my ($svc_zip, $svc_plus4) = split('-', $location->zip);
+  my ($zip, $plus4) = split('-', $location->zip);
+
+  # and the _real_ location as 'P2PZipcode'
+  my $svc_location = $location;
+  if ( $cust_bill_pkg->pkgnum ) {
+    $svc_location = $cust_bill_pkg->cust_pkg->cust_location;
+  }
+  my ($svc_zip, $svc_plus4) = split('-', $svc_location->zip);
 
   my $startdate =
     DateTime->from_epoch( epoch => $cust_bill->_date )->strftime('%m-%d-%Y');
@@ -151,8 +159,8 @@ sub build_item {
     'OrigNumber'      => '',
     'TermNumber'      => '',
     'BillToNumber'    => '',
-    'Zipcode'         => $self->{bill_zip},
-    'Plus4'           => ($self->{bill_plus4} ||= '0000'),
+    'Zipcode'         => $zip,
+    'Plus4'           => ($plus4 ||= '0000'),
     'P2PZipcode'      => $svc_zip,
     'P2PPlus4'        => ($svc_plus4 ||= '0000'),
     # we don't support Order Placement/Approval zip codes
@@ -205,27 +213,31 @@ sub build_item {
     while ( my $cdr = $cdrs->fetch ) {
       my $calldate =
         DateTime->from_epoch( epoch => $cdr->startdate )->strftime('%m-%d-%Y');
-      # determine the tax situs rule; it's different (probably more accurate) 
-      # if the call has PSTN phone numbers at both ends
-      my $tsr = $TSR_CALL_OTHER;
-      if ( $cdr->charged_party =~ /^\d{10}$/ and
-           $cdr->src           =~ /^\d{10}$/ and
-           $cdr->dst           =~ /^\d{10}$/ ) {
-        $tsr = $TSR_CALL_NPANXX;
-      }
       my %hash = (
         %base_item,
         'LineNumber'      => 'C' . $cdr->acctid,
-        'OrigNumber'      => $cdr->src,
-        'TermNumber'      => $cdr->dst,
-        'BillToNumber'    => $cdr->charged_party,
+        'OrigNumber'      => '',
+        'TermNumber'      => '',
+        'BillToNumber'    => '',
         'TransDate'       => $calldate,
         'Revenue'         => $cdr->rated_price, # 4 decimal places
         'Units'           => 0, # right?
         'CallDuration'    => $cdr->duration,
-        'TaxSitusRule'    => $tsr,
+        'TaxSitusRule'    => $TSR_CALL_OTHER,
         'TransTypeCode'   => $taxproduct,
       );
+      # determine the tax situs rule; it's different (probably more accurate) 
+      # if the call has PSTN phone numbers at both ends
+      if ( $cdr->charged_party =~ /^\d{10}$/ and
+           $cdr->src           =~ /^\d{10}$/ and
+           $cdr->dst           =~ /^\d{10}$/ and
+           !$cdr->is_tollfree ) {
+        $hash{TaxSitusRule} = $TSR_CALL_NPANXX;
+        $hash{OrigNumber}   = $cdr->src;
+        $hash{TermNumber}   = $cdr->dst;
+        $hash{BillToNumber} = $cdr->charged_party;
+      }
+
       push @items, \%hash;
 
     } # while ($cdrs->fetch)
@@ -246,11 +258,13 @@ sub build_item {
         if !$taxproduct;
 
     my $tsr = $TSR_GENERAL;
+    # when billing on cancellation there are no units
+    my $units = $self->{cancel} ? 0 : $cust_bill_pkg->units;
     my %hash = (
       %base_item,
       'LineNumber'      => 'R' . $billpkgnum,
       'Revenue'         => $recur_without_usage, # 4 decimal places
-      'Units'           => $cust_bill_pkg->units,
+      'Units'           => $units,
       'TaxSitusRule'    => $tsr,
       'TransTypeCode'   => $taxproduct,
     );
@@ -309,11 +323,19 @@ sub make_taxlines {
 
   # assemble the request hash
   my $request = $self->build_request;
+  if (!$request) {
+    warn "no taxable items in invoice; skipping SureTax request\n" if $DEBUG;
+    return;
+  }
 
-  warn "sending SureTax request\n" if $DEBUG;
+  warn "encoding SureTax request\n" if $DEBUG;
   my $request_json = $json->encode($request);
   warn $request_json if $DEBUG > 1;
 
+  my $host = $conf->config('suretax-hostname');
+  $host ||= 'testapi.taxrating.net';
+
+  warn "sending SureTax request\n" if $DEBUG;
   # We are targeting the "V05" interface:
   # - accepts both telecom and general sales transactions
   # - produces results broken down by "invoice" (Freeside line item)
@@ -325,8 +347,11 @@ sub make_taxlines {
     'Accept'        => 'application/json',
   );
 
+  warn 'received SureTax response: '. $http_response->status_line. "\n"
+    if $DEBUG;
+  die $http_response->status_line. "\n" unless $http_response->is_success;
+
   my $raw_response = $http_response->content;
-  warn "received response\n" if $DEBUG;
   warn $raw_response if $DEBUG > 2;
   my $response;
   if ( $raw_response =~ /^<\?xml/ ) {
@@ -335,8 +360,10 @@ sub make_taxlines {
     $response = XMLin( $raw_response );
     $raw_response = $response->{content};
   }
+
+  warn "decoding SureTax response\n" if $DEBUG;
   $response = eval { $json->decode($raw_response) }
-    or die "$raw_response\n";
+    or die "Can't JSON-decode response: $raw_response\n";
 
   # documentation implies this might be necessary
   $response = $response->{'d'} if exists $response->{'d'};
@@ -354,6 +381,7 @@ sub make_taxlines {
   }
 
   return if !$response->{GroupList};
+  warn "creating FS objects from SureTax data\n" if $DEBUG;
   foreach my $taxable ( @{ $response->{GroupList} } ) {
     # each member of this array here corresponds to what SureTax calls an
     # "invoice" and we call a "line item". The invoice number is 
@@ -399,6 +427,7 @@ sub make_taxlines {
       });
     }
   }
+  warn "TaxEngine/suretax.pm make_taxlines done; returning FS objects\n" if $DEBUG;
   return @elements;
 }