changes for cardfortress
authorMark Wells <mark@freeside.biz>
Fri, 1 Feb 2013 04:16:10 +0000 (20:16 -0800)
committerMark Wells <mark@freeside.biz>
Fri, 1 Feb 2013 04:16:10 +0000 (20:16 -0800)
BatchPayment/Batch.pm
BatchPayment/Item.pm
BatchPayment/Processor.pm
BatchPayment/Transport/HTTPS.pm

index 0d6176b..16f1738 100644 (file)
@@ -21,20 +21,42 @@ but usually must be a positive integer, if it's used at all.
 Processor modules may include C<batch_id> in a reply batch ONLY if 
 it is guaranteed to match the batch_id of a request batch AND all 
 the items in the reply batch come from that request batch.  Otherwise, 
-C<batch_id> must be null.  It must always be null when using one-way
+C<batch_id> must be undef.  It must always be undef when using one-way
 (receive-only) workflow, since there are no request batches.
 
+=item process_date - The intended processing date for a request batch.
+If not set, it will default to the start of the next day; if that's  
+not what you want, set it explicitly.
+
 =item items - An arrayref of L<Business::BatchPayment::Item> objects
 included in the batch.
 
+=item num
+
+If your processor uses C<format_header> and C<format_item>, this will 
+be set to 0 by C<format_header> and incremented every time C<format_item>
+is called.  Convenient for formats that require record numbers.
+
 =back
 
+=head1 METHODS
+
+=over 4
+
+=item totals
+
+Returns a hash containing 'credit_count', 'credit_sum', 'payment_count', 
+and 'payment_sum'.  These are the number of credits, sum of credit amounts,
+number of payments, and sum of payment amounts.
+
 =cut
 
 package Business::BatchPayment::Batch;
 
 use strict;
 use Moose;
+use Moose::Util::TypeConstraints;
+use DateTime;
 
 has incoming => (
   is => 'rw',
@@ -59,4 +81,38 @@ has items => (
   default => sub { [] },
 );
 
+class_type 'DateTime';
+coerce 'DateTime', from 'Int', via { DateTime->from_epoch($_) };
+
+has process_date    => (
+  is => 'rw',
+  isa => 'DateTime',
+  coerce => 1,
+  default => sub {
+#    warn "No batch process date set; assuming tomorrow.\n";
+    DateTime->today->add(days => 1);
+  },
+);
+
+has num => (
+  is => 'rw',
+  isa => 'Maybe[Int]',
+);
+
+sub totals {
+  my $self = shift;
+  my %totals = map {$_ => 0}
+    qw(credit_count credit_sum payment_count payment_sum);
+  foreach ($self->elements) {
+    if ($_->action eq 'credit') {
+      $totals{credit_count}++;
+      $totals{credit_sum} += $_->amount;
+    } elsif ( $_->action eq 'payment') {
+      $totals{payment_count}++;
+      $totals{payment_sum} += $_->amount;
+    }
+  }
+  %totals;
+}
+
 1;
index d9a3ec7..8a203ac 100644 (file)
@@ -106,6 +106,10 @@ Company name.
 Billing address fields.  Credit card processors may use these (especially
 zip) for authentication.
 
+=item phone
+
+Customer phone number.
+
 =cut
 
 has [ qw(
@@ -119,6 +123,7 @@ has [ qw(
   state
   country
   zip
+  phone
 ) ] => ( is => 'rw', isa => 'Str', default => '' );
 
 =back
@@ -129,7 +134,9 @@ has [ qw(
 
 =item process_date
 
-The date requested for processing.
+The date requested for processing.  This is meaningful only if the 
+processor allows different processing dates for items in the same 
+batch.
 
 =item invoice_number
 
@@ -193,7 +200,54 @@ Credit card expiration, MMYY format.
 =cut
 
 has card_number     => ( is => 'rw', isa => 'Str' );
-has expiration      => ( is => 'rw', isa => 'Str' );
+has ['expiration_month', 'expiration_year'] => ( is => 'rw', isa => 'Int' );
+
+sub expiration {
+  # gets/sets expiration_month and _year in MMYY format
+  my $self = shift;
+  my $arg = shift;
+  if ( $arg ) {
+    # well, we said it's in MMYY format
+    my ($m, $y) = _parse_expiration($arg);
+    $self->expiration_month($m);
+    $self->expiration_year($y);
+  }
+  return sprintf('%02d/%02d',
+    $self->expiration_month,
+    $self->expiration_year % 2000);
+}
+
+sub _parse_expiration {
+  my $arg = shift;
+  if ( $arg =~ /^(\d\d)(\d\d)$/ ) {
+    return ($1, 2000 + $2);
+  } elsif ( $arg =~ /^(\d\d?)\W(\d\d)$/ ) {
+    return ($1, 2000 + $2);
+  } elsif ( $arg =~ /^(\d\d?)\W(\d\d\d\d)$/ ) {
+    return ($1, $2);
+  } elsif ( $arg =~ /^(\d\d?)\W\d\d?\W(\d\d\d\d)$/) {
+    return ($1, $3);
+  } else {
+    die "can't parse expiration date '$arg'";
+  }
+}
+
+sub payinfo {
+  # gets/sets either the card number, or the account number + routing code
+  # depending on the payment type
+  my $self = shift;
+  if ( $self->payment_type eq 'CC' ) {
+    $self->card_number(@_);
+  } elsif ( $self->payment_type eq 'ECHECK' ) {
+    my $arg = shift;
+    if ( $arg ) {
+      $arg =~ /^(\d+)@(\d+)$/ or die "Validation failed for payinfo";
+      $self->account_number($1);
+      $self->routing_code($2);
+    }
+    return ($self->account_number . '@' . $self->routing_code);
+  }
+}
 
 =back
 
@@ -273,6 +327,15 @@ has [qw(
 
 has check_number => ( is => 'rw', isa => 'Int' );
 
+around 'BUILDARGS' => sub {
+  my ($orig, $self, %args) = @_;
+  if ( $args{expiration} ) {
+    @args{'expiration_month', 'expiration_year'} =
+      _parse_expiration($args{expiration}); 
+  }
+  $self->$orig(%args);
+};
+
 __PACKAGE__->meta->make_immutable;
 
 1;
index e02259a..a148c16 100644 (file)
@@ -219,7 +219,7 @@ sub submit {
   warn $request if $self->debug >= 2;
   $self->transport->upload($request);
 }
-;
+
 sub receive {
   my $self = shift;
   my @responses = $self->transport->download;
@@ -237,9 +237,11 @@ sub format_request {
   my $self = shift;
   my $batch = shift;
   my $output = $self->format_header($batch);
+  $batch->num(0);
   foreach my $item ($batch->elements) {
     try {
       $output .= $self->format_item($item, $batch);
+      $batch->num( $batch->num + 1 );
     } catch {
       $self->format_error($item, $_);
     };
@@ -253,12 +255,14 @@ sub parse_response {
   my $input = shift;
   my $batch = Business::BatchPayment->create(Batch =>
     incoming => $self->incoming,
-    batch_id => $self->parse_batch_id($input)
+    batch_id => $self->parse_batch_id($input),
+    num      => 0,
   );
   while ( $input =~ s/(.*)\n//m ) {
     my $row = $1;
     try { 
       $batch->push( $self->parse_item($row) );
+      $batch->num( $batch->num + 1 );
     } catch {
       $self->parse_error($row, $_);
     };
index fdb2c35..0eb783b 100644 (file)
@@ -14,29 +14,36 @@ use Net::HTTPS::Any 0.10;
 with 'Business::BatchPayment::Transport';
 
 has [ qw( host port get_path put_path ) ] => ( 
-  is => 'ro',
+  is => 'rw',
   isa => 'Str'
 );
 
 has 'content_type' => (
   is => 'rw',
   isa => 'Str',
-  default => 'text/plain'
+  default => '', # application/x-www-form-urlencoded
 );
 
 sub https_post {
   my $self = shift;
   my $path = shift;
   my $content = shift;
-
-  warn "starting https_post...\n" if $self->debug;
-  my ( $page, $response, %reply_headers ) = Net::HTTPS::Any::https_post(
+  my %post = (
     host => $self->host,
     port => $self->port,
     path => $path,
-    content => $content,
-    debug => ($self->debug >= 3),
+    debug => ($self->debug > 3 ? 1 : 0),
+    'Content-Type' => $self->content_type
   );
+  if (ref $content and ref $content eq 'HASH') {
+    $post{'args'} = $content;
+  } else {
+    $post{'content'} = $content;
+  }
+
+  warn "starting https_post...\n" if $self->debug;
+  my ( $page, $response, %reply_headers ) = Net::HTTPS::Any::https_post(%post);
+
   warn "PAGE:\n$page\n\nRESPONSE:\n$response\n\n" if $self->debug >= 2;
   return ($page, $response, %reply_headers);
 }