Initial release
authormark <mark>
Tue, 18 May 2010 18:55:03 +0000 (18:55 +0000)
committermark <mark>
Tue, 18 May 2010 18:55:03 +0000 (18:55 +0000)
Changes [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
NMI.pm [new file with mode: 0644]
README [new file with mode: 0644]
t/00-load.t [new file with mode: 0644]

diff --git a/Changes b/Changes
new file mode 100644 (file)
index 0000000..609d30a
--- /dev/null
+++ b/Changes
@@ -0,0 +1,5 @@
+Revision history for Business-OnlinePayment-NMI
+
+0.01    Tue May 18 11:53:39 PDT 2010
+        Initial release
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..a88f430
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,6 @@
+Changes
+MANIFEST
+Makefile.PL
+README
+NMI.pm
+t/00-load.t
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644 (file)
index 0000000..02c2a8e
--- /dev/null
@@ -0,0 +1,20 @@
+use strict;
+use warnings;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+    NAME                => 'Business::OnlinePayment::NMI',
+    AUTHOR              => 'Mark Wells <mark@freeside.biz>',
+    VERSION_FROM        => 'NMI.pm',
+    ($ExtUtils::MakeMaker::VERSION >= 6.3002
+      ? ('LICENSE'=> 'perl')
+      : ()),
+    PL_FILES            => {},
+    PREREQ_PM => {
+        'Test::More' => 0,
+        'Business::OnlinePayment' => 3,
+        'Business::OnlinePayment::HTTPS' => 0,
+    },
+    dist                => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
+    clean               => { FILES => 'Business-OnlinePayment-NMI-*' },
+);
diff --git a/NMI.pm b/NMI.pm
new file mode 100644 (file)
index 0000000..f11f4be
--- /dev/null
+++ b/NMI.pm
@@ -0,0 +1,310 @@
+package Business::OnlinePayment::NMI;
+
+use strict;
+use Carp;
+use Business::OnlinePayment 3;
+use Business::OnlinePayment::HTTPS;
+use Digest::MD5 qw(md5_hex);
+use URI::Escape;
+use vars qw($VERSION @ISA $DEBUG);
+
+@ISA = qw(Business::OnlinePayment::HTTPS);
+$VERSION = '0.01';
+
+$DEBUG = 0;
+
+sub _info {
+  {
+    'info_compat'           => '0.01',
+    'gateway_name'          => 'Network Merchants',
+    'gateway_url'           => 'https://www.nmi.com',
+    'module_version'        => $VERSION,
+    'supported_types'       => [ 'CC', 'ECHECK' ],
+    'supported_actions'     => {
+                                  CC => [
+                                    'Normal Authorization',
+                                    'Authorization Only',
+                                    'Post Authorization',
+                                    'Credit',
+                                    'Void',
+                                    ],
+                                  ECHECK => [
+                                    'Normal Authorization',
+                                    'Credit',
+                                    'Void',
+                                    ],
+    },
+  };
+}
+
+my %actions = (
+  'normal authorization' => 'sale',
+  'authorization only'   => 'auth',
+  'post authorization'   => 'capture',
+  'credit'               => 'refund',
+  'void'                 => 'void',
+);
+my %types = (
+  'cc'  => 'creditcard',
+  'echeck' => 'check',
+);
+
+my %fields = (
+# NMI Direct Post API, June 2007
+  action          => 'type', # special
+  login           => 'username',
+  password        => 'password',
+  card_number     => 'ccnumber',
+  expiration      => 'ccexp',
+  name            => 'checkname',
+  routing_code    => 'checkaba',
+  account_number  => 'checkaccount',
+  account_holder_type => 'account_holder_type',
+  account_type    => 'account_type',
+  amount          => 'amount',
+  cvv2            => 'cvv',
+  payment         => 'payment', # special
+  description     => 'orderdescription',
+  invoice_number  => 'orderid',
+  customer_ip     => 'ipaddress',
+  tax             => 'tax',
+  freight         => 'shipping',
+  po_number       => 'ponumber',
+  first_name      => 'firstname',
+  last_name       => 'lastname',
+  company         => 'company',
+  address         => 'address1',
+  city            => 'city',
+  state           => 'state',
+  zip             => 'zip',
+  country         => 'country',
+  order_number    => 'transactionid', # used for capture/void/refund
+);
+
+$fields{"ship_$_"} = 'shipping_'.$fields{$_} 
+  foreach(qw(first_name last_name company address city state zip country)) ;
+
+my %required = (
+'ALL'             => [ qw( type username password payment ) ],
+'sale'            => [ 'amount' ],
+'sale:creditcard' => [ 'ccnumber', 'ccexp' ],
+'sale:check'      => [ qw( checkname checkaba checkaccount account_holder_type account_type ) ],
+'auth:creditcard' => [ qw( amount ccnumber ccexp ) ],
+'capture'         => [ 'amount', 'transactionid' ],
+'refund'          => [ 'amount', 'transactionid' ],
+'void'            => [ 'transactionid' ],
+# not supported: update
+),
+
+my %optional = (
+'ALL'             => [],
+'sale'            => [ qw( orderdescription orderid ipaddress tax 
+                           shipping ponumber firstname lastname company 
+                           address1 city state zip country phone fax email 
+                           shipping_firstname shipping_lastname
+                           shipping_company shipping_address1 shipping_city 
+                           shipping_state shipping_zip shipping_country 
+                           ) ],
+'sale:creditcard' => [ 'cvv' ],
+'sale:check' => [],
+'auth:creditcard' => [ qw( orderdescription orderid ipaddress tax 
+                           shipping ponumber firstname lastname company 
+                           address1 city state zip country phone fax email 
+                           shipping_firstname shipping_lastname
+                           shipping_company shipping_address1 shipping_city 
+                           shipping_state shipping_zip shipping_country 
+                           cvv ) ],
+'capture'         => [ 'orderid' ],
+'refund'          => [ 'amount' ],
+);
+
+my %failure_status = (
+200 => 'decline',
+201 => 'decline',
+202 => 'nsf',
+203 => 'nsf',
+223 => 'expired',
+250 => 'pickup',
+252 => 'stolen',
+# add others here as needed; very little code uses failure_status at present
+);
+
+sub set_defaults {
+    my $self = shift;
+    $self->server('secure.networkmerchants.com');
+    $self->port('443');
+    $self->path('/api/transact.php');
+    $self->build_subs(qw(avs_code cvv2_response failure_status));
+}
+
+sub map_fields {
+  my($self) = shift;
+
+  my %content = $self->content();
+
+  if($self->test_transaction) {
+    # Public test account.
+    $content{'login'} = 'demo';
+    $content{'password'} = 'password';
+  }
+
+  $content{'payment'} = $types{lc($content{'type'})} or die "Payment method '$content{type}' not supported.\n";
+  $content{'action'} = $actions{lc($content{'action'})} or die "Transaction type '$content{action}' not supported.\n";
+
+  $content{'expiration'} =~ s/\D//g;
+
+  $content{'account_type'} ||= 'personal checking';
+  @content{'account_holder_type', 'account_type'} = 
+    map {lc} split /\s/, $content{'account_type'};
+  $content{'ship_name'} = $content{'ship_first_name'}.' '.$content{'ship_last_name'};
+  $self->content(%content);
+}
+
+sub submit {
+    my($self) = @_;
+
+    $self->map_fields();
+
+    $self->remap_fields(%fields);
+
+    my %content = $self->content;
+    my $type = $content{'type'}; # what we call "action"
+    my $payment = $content{'payment'}; # what we call "type"
+    if ( $DEBUG >= 3  ) {
+      warn "content:$_ => $content{$_}\n" foreach keys %content;
+    }
+
+    my @required_fields = ( @{$required{'ALL'}} );
+    push @required_fields, @{$required{$type}} if exists($required{$type});
+    push @required_fields, @{$required{"$type:$payment"}} if exists($required{"$type:$payment"});
+
+    $self->required_fields(@required_fields);
+
+    my @allowed_fields = @required_fields;
+    push @allowed_fields, @{$optional{'ALL'}};
+    push @allowed_fields, @{$optional{$type}} if exists($optional{$type});
+    push @allowed_fields, @{$optional{"$type:$payment"}} if exists($required{"$type:$payment"});
+
+    my %post_data = $self->get_fields(@allowed_fields);
+
+    if ( $DEBUG ) {
+      warn "post_data:$_ => $post_data{$_}\n" foreach keys %post_data;
+    }
+
+    my($page,$server_response) = $self->https_post(\%post_data);
+    if ( $DEBUG ) {
+      warn "response page: $page\n";
+    }
+
+    my $response;
+    if ($server_response =~ /200/){
+      $response = {map { split '=', $_, 2 } split '&', $page};
+    }
+    else {
+      die "HTTPS error: '$server_response'\n";
+    }
+
+    $response->{$_} =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg
+      foreach keys %$response;
+
+    if ( $DEBUG ) {
+      warn "response:$_ => $response->{$_}\n" foreach keys %$response;
+    }
+
+    $self->is_success(0);
+    my $error;
+    if( $response->{response} == 1 ) {
+      $self->is_success(1);
+    }
+    elsif( $response->{response} == 2 ) {
+      $error = $response->{responsetext};
+      my $code = $response->{response_code};
+      $self->failure_status($failure_status{$code}) if exists($failure_status{$code});
+    }
+    elsif( $response->{response} == 3 ) {
+      $error = "Transaction error: '".$response->{responsetext};
+    }
+    else {
+      $error = "Could not interpret server response: '$page'";
+    }
+    $self->order_number($response->{transactionid});
+    $self->authorization($response->{authcode});
+    $self->avs_code($response->{avsresponse});
+    $self->cvv2_response($response->{cvvresponse});
+    $self->result_code($response->{response_code});
+    $self->error_message($error);
+    $self->server_response($response);
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Business::OnlinePayment::NMI - Network Merchants backend for Business::OnlinePayment
+
+=head1 SYNOPSIS
+
+  use Business::OnlinePayment;
+
+  my $tx = new Business::OnlinePayment("NMI");
+  $tx->content(
+      login          => 'mylogin',
+      password       => 'mypass',
+      action         => 'Normal Authorization',
+      description    => 'Business::OnlinePayment test',
+      amount         => '49.95',
+      invoice_number => '100100',
+      name           => 'Tofu Beast',
+      card_number    => '46464646464646',
+      expiration     => '11/08',
+      address        => '1234 Bean Curd Lane, San Francisco',
+      zip            => '94102',
+  );
+  $tx->submit();
+
+  if($tx->is_success()) {
+      print "Card processed successfully: ".$tx->authorization."\n";
+  } else {
+      print "Card was rejected: ".$tx->error_message."\n";
+  }
+
+=head1 DESCRIPTION
+
+For detailed information see L<Business::OnlinePayment>.
+
+=head1 SUPPORTED TRANSACTION TYPES
+
+=head2 Credit Card
+
+Normal Authorization, Authorization Only, Post Authorization, Void, Credit.
+
+=head2 Check
+
+Normal Authorization, Void, Credit.
+
+=head1 NOTES
+
+Credit is handled using NMI's 'refund' action, which applies the credit against 
+a specific payment.
+
+Post Authorization, Void, and Credit require C<order_number> to be set with the 
+transaction ID of the previous authorization.
+
+=head1 COMPATIBILITY
+
+This module implements the NMI Direct Post API, June 2007 revision.
+
+=head1 AUTHOR
+
+Mark Wells <mark@freeside.biz>
+
+Based in part on Business::OnlinePayment::USAePay by Jeff Finucane 
+<jeff@cmh.net>.
+
+=head1 SEE ALSO
+
+perl(1). L<Business::OnlinePayment>.
+
+=cut
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..f4109ea
--- /dev/null
+++ b/README
@@ -0,0 +1,47 @@
+Business-OnlinePayment-NMI
+A Business::OnlinePayment backend for credit card and electronic check 
+processing through the Network Merchants Inc. system.
+
+INSTALLATION
+
+To install this module, run the following commands:
+
+       perl Makefile.PL
+       make
+       make test
+       make install
+
+SUPPORT AND DOCUMENTATION
+
+After installing, you can find documentation for this module with the
+perldoc command.
+
+    perldoc Business::OnlinePayment::NMI
+
+You can also look for information at:
+
+    RT, CPAN's request tracker
+        http://rt.cpan.org/NoAuth/Bugs.html?Dist=Business-OnlinePayment-NMI
+
+    AnnoCPAN, Annotated CPAN documentation
+        http://annocpan.org/dist/Business-OnlinePayment-NMI
+
+    CPAN Ratings
+        http://cpanratings.perl.org/d/Business-OnlinePayment-NMI
+
+    Search CPAN
+        http://search.cpan.org/dist/Business-OnlinePayment-NMI/
+
+Support for commercial users is available from:
+
+    Freeside Internet Services, Inc.
+        http://www.freeside.biz
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2010 Mark Wells <mark@freeside.biz>
+Copyright (C) 2010 Freeside Internet Services, Inc.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
diff --git a/t/00-load.t b/t/00-load.t
new file mode 100644 (file)
index 0000000..6116487
--- /dev/null
@@ -0,0 +1,9 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+       use_ok( 'Business::OnlinePayment::NMI' );
+}
+
+diag( "Testing Business::OnlinePayment::NMI $Business::OnlinePayment::NMI::VERSION, Perl $], $^X" );