From bfcab12bcc31b0445689f03593a8bdbaa84aa4a5 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 18 May 2010 18:55:03 +0000 Subject: [PATCH] Initial release --- Changes | 5 + MANIFEST | 6 ++ Makefile.PL | 20 ++++ NMI.pm | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README | 47 +++++++++ t/00-load.t | 9 ++ 6 files changed, 397 insertions(+) create mode 100644 Changes create mode 100644 MANIFEST create mode 100644 Makefile.PL create mode 100644 NMI.pm create mode 100644 README create mode 100644 t/00-load.t diff --git a/Changes b/Changes new file mode 100644 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 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 index 0000000..02c2a8e --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,20 @@ +use strict; +use warnings; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'Business::OnlinePayment::NMI', + AUTHOR => 'Mark Wells ', + 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 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. + +=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 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 + +Based in part on Business::OnlinePayment::USAePay by Jeff Finucane +. + +=head1 SEE ALSO + +perl(1). L. + +=cut + diff --git a/README b/README new file mode 100644 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 +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 index 0000000..6116487 --- /dev/null +++ b/t/00-load.t @@ -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" ); -- 2.11.0