[open-ils-commits] r14309 - in trunk/Open-ILS/src: perlmods/OpenILS/Application support-scripts support-scripts/test-scripts (erickson)

svn at svn.open-ils.org svn at svn.open-ils.org
Thu Oct 8 13:23:05 EDT 2009


Author: erickson
Date: 2009-10-08 13:23:01 -0400 (Thu, 08 Oct 2009)
New Revision: 14309

Added:
   trunk/Open-ILS/src/support-scripts/test-scripts/payment_test.pl
Modified:
   trunk/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm
   trunk/Open-ILS/src/support-scripts/oils_header.pl
   trunk/Open-ILS/src/support-scripts/test-scripts/notes.pl
Log:

Patch from Joe Atzberger and Lebbeous Fogle-Weekley:

- provides numerous cleanups to the creditcard.pm module
- test script for testing credit card transactions
- currently support authorizenet and paypal (requires account logins to test)
- other miscellaneous format cleanups



Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm	2009-10-08 15:51:51 UTC (rev 14308)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm	2009-10-08 17:23:01 UTC (rev 14309)
@@ -2,6 +2,8 @@
 # Copyright (C) 2008 Niles Ingalls 
 # Niles Ingalls <nilesi at zionsville.lib.in.us>
 # Bill Erickson <erickson at esilibrary.com>
+# Joe Atzberger <atz at esilibrary.com>
+# Lebbeous Fogle-Weekley <lebbeous at esilibrary.com>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -17,45 +19,100 @@
 use base qw/OpenSRF::Application/;
 use strict; use warnings;
 
-use DateTime;
-use DateTime::Format::ISO8601;
-use OpenILS::Application::AppUtils;
-use OpenSRF::Utils qw/:datetime/;
-use OpenILS::Event;
-use OpenSRF::EX qw(:try);
-use OpenSRF::Utils::Logger qw(:logger);
-use OpenILS::Utils::Fieldmapper;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenILS::Const qw/:const/;
-use OpenSRF::Utils::SettingsClient;
 use Business::CreditCard;
-use Business::CreditCard::Object;
 use Business::OnlinePayment;
+
+use OpenILS::Event;
+use OpenSRF::Utils::Logger qw/:logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Application::AppUtils;
 my $U = "OpenILS::Application::AppUtils";
 
+my @ALLOWED_PROCESSORS = qw/AuthorizeNet PayPal/;
 
+# Given the argshash from process_payment(), this helper function just finds
+# a function in the current namespace named "bop_args_{processor}" and calls
+# it with $argshash as an argument, returning the result, or returning an
+# empty hash if it can't find such a function.
+sub get_bop_args_filler {
+    no strict 'refs';
+
+    my $argshash = shift;
+    my $funcname = "bop_args_" . $argshash->{processor};
+    return &{$funcname}($argshash) if defined &{$funcname};
+    return ();
+}
+
+# Provide default arguments for calls using the AuthorizeNet processor
+sub bop_args_AuthorizeNet {
+    my $argshash = shift;
+    if ($argshash->{server}) {
+        return (
+            # One might provide "test.authorize.net" here.
+            Server => $argshash->{server},
+        );
+    }
+    else {
+        return ();
+    }
+}
+
+# Provide default arguments for calls using the PayPal processor
+sub bop_args_PayPal {
+    my $argshash = shift;
+    return (
+        Username => $argshash->{login},
+        Password => $argshash->{password},
+        Signature => $argshash->{signature}
+    );
+}
+
 __PACKAGE__->register_method(
     method    => 'process_payment',
     api_name  => 'open-ils.credit.process',
     signature => {
-        desc   => 'Creates a new provider',
+        desc   => 'Process a payment via a supported processor (AuthorizeNet, Paypal)',
         params => [
-            { desc => 'Authentication token', type => 'string' },
-            { desc => q/Hash of arguments.  Options include:
-                XXX add docs as API stablilizes...
+            { desc => q/Hash of arguments with these keys:
+                patron_id: Not a barcode, but a patron's internal ID
+                processor: the transaction "clearing house" (e.g. PayPal)
+                    login: supplied by processor to institution for their API
+                 password: supplied by processor to institution for their API
+                       cc: credit card number
+                     cvv2: 3 or 4 digits from back of card
+                   amount: transaction value
+                 testmode: optional (default: NO, i.e. a REAL transaction), note this is different than targeting the processor's test server
+                   action: optional (default: Normal Authorization)
+                signature: optional (required by some processor APIs)
+               first_name: optional (default: patron's first_given_name field)
+                last_name: optional (default: patron's family_name field)
+                  address: optional (default: patron's street1 field)
+                     city: optional (default: patron's city field)
+                    state: optional (default: patron's state field)
+                      zip: optional (default: patron's zip field)
+                  country: optional (some processor APIs. 2 letter code.)
+              description: optional
+                   server: optional (for testing some APIs, i.e. AuthorizeNet)
                 /, type => 'hash' }
         ],
-        return => { desc => 'Hash of status information', type=>'hash' }
+        return => { desc => 'Hash of status information', type =>'hash' }
     }
 );
 
 sub process_payment {
-    my $self     = shift;
-    my $client   = shift;
-    my $argshash = shift;
+    my ($self, $client, $argshash) = @_; # $client is unused in this sub
 
-    my $e = new_editor();
-    my $patron = $e->retrieve_actor_user(
+    # Confirm required arguments.
+    return OpenILS::Event->new('BAD_PARAMS')
+      unless $argshash
+         and $argshash->{login}
+         and $argshash->{password}
+         and $argshash->{processor}
+         and $argshash->{cc};
+
+     # A valid patron_id is also required.
+     my $e = new_editor();
+     my $patron = $e->retrieve_actor_user(
         [
             $argshash->{patron_id},
             {
@@ -65,164 +122,132 @@
         ]
     ) or return $e->event;
 
-    return OpenILS::Event->new('BAD_PARAMS')
-      unless $argshash->{login}
-          and $argshash->{password}
-          and $argshash->{action};
-
-    if ( $argshash->{processor} eq 'PayPal' ) {    
-        #  XXX not ready for prime time
-        return handle_paypal($e, $argshash, $patron);
-
-    } elsif ( $argshash->{processor} eq 'AuthorizeNet' ) {
-        return handle_authorizenet($e, $argshash, $patron);
+    if (grep { $_ eq $argshash->{processor} } @ALLOWED_PROCESSORS) {
+        return dispatch($argshash, $patron);
+    } else {
+        return OpenILS::Event->new('BAD_PARAMS');   # no supported processor
     }
 }
 
-sub handle_paypal {
-    my($e, $argshash, $patron) = @_;
+sub prepare_bop_content {
+    my ($argshash, $patron, $cardtype) = @_;
 
-    require Business::PayPal::API;
-    require Business::OnlinePayment::PayPal;
-    my $card = Business::CreditCard::Object->new( $argshash->{cc} );
-
-    $logger->debug("applying paypal payment");
-
-    if ( !$card->is_valid ) {
-        return {
-            statusText       => "should return address:(patron_id):",
-            processor        => $argshash->{processor},
-            testmode         => $argshash->{testmode},
-            card             => $card->number(),
-            expiration       => $argshash->{expiration},
-            name             => $patron->first_given_name,
-            patron_id        => $patron->id,
-            patron_patron_id => $patron->mailing_address,
-            statusCode       => 500
-        };
+    my %content;
+    foreach (qw/
+        login
+        password
+        description
+        first_name
+        last_name
+        amount
+        expiration
+        cvv2
+        address
+        city
+        state
+        zip
+        country/) {
+        if (exists $argshash->{$_}) {
+            $content{$_} = $argshash->{$_};
+        }
     }
+    
+    $content{action}       = $argshash->{action} || "Normal Authorization";
+    $content{type}         = $cardtype;      #'American Express', 'VISA', 'MasterCard'
+    $content{card_number}  = $argshash->{cc};
+    $content{customer_id}  = $patron->id;
+    
+    $content{first_name} ||= $patron->first_given_name;
+    $content{last_name}  ||= $patron->family_name;
 
-    my $type = $card->type();
+    $content{FirstName}    = $content{first_name};   # kludge mcugly for PP
+    $content{LastName}     = $content{last_name};
 
-    if ( substr( $type, -5, 5 ) =~ / card/ ) {
-        $type = substr( $type, 0, -5 );
-    }
 
-    my $transaction = Business::OnlinePayment->new(
-        $argshash->{processor},
-        "Username"  => $argshash->{PayPal_Username},
-        "Password"  => $argshash->{PayPal_Password},
-        "Signature" => $argshash->{PayPal_Signature}
-    );
+    # Especially for the following fields, do we need to support different
+    # mapping of fields for different payment processors, particularly ones
+    # in other countries?
+    $content{address}    ||= $patron->mailing_address->street1;
+    $content{city}       ||= $patron->mailing_address->city;
+    $content{state}      ||= $patron->mailing_address->state;
+    $content{zip}        ||= $patron->mailing_address->post_code;
 
-    $transaction->content(
-        action      => $argshash->{action},
-        amount      => $argshash->{amount},
-        type        => "$type",
-        card_number => $card->number(),
-        expiration  => $argshash->{expiration},
-        cvv2        => $argshash->{cvv2},
-        name => $patron->first_given_name . ' ' . $patron->family_name,
-        address => $patron->mailing_address->street1,
-        city    => $patron->mailing_address->city,
-        state   => $patron->mailing_address->state,
-        zip     => $patron->mailing_address->post_code
-    );
+    %content;
+}
 
-    $transaction->test_transaction(1); # XXX
-    $transaction->submit;
+sub dispatch {
+    my ($argshash, $patron) = @_;
+    
+    # The validate() sub is exported by Business::CreditCard.
+    if (!validate($argshash->{cc})) {
+        # Although it might help a troubleshooter, it's probably not a good
+        # idea to put the credit card number in the log file.
+        $logger->warn("Credit card number invalid");
 
-    if ( $transaction->is_success ) {
+        # The idea of returning a hashref with statusText and statusCode
+        # comes from an older version handle_authorizenet(), but I'm not
+        # sure it's the best thing to do, really.
         return {
-            statusText => "Card approved: ".$transaction->authorization,
-            statusCode    => 200,
-            approvalCode  => $transaction->authorization,
-            CorrelationID => $transaction->correlationid
-        };
-
-    } else {
-        return {
-            statusText => "Card declined: " . $transaction->error_message,
+            statusText => "Credit card number invalid",
             statusCode => 500
-
         };
     }
-}
 
-sub handle_authorizenet {
-    my($e, $argshash, $patron) = @_;
+    # cardtype() also comes from Business::CreditCard.  It is not certain that
+    # a) the card type returned by this method will be suitable input for
+    #   a payment processor, nor that
+    # b) it is even necessary to supply this argument to processors in all
+    #   cases.  Testing this with several processors would be a good idea.
+    (my $cardtype = cardtype($argshash->{cc})) =~ s/ card//;
 
-    require Business::OnlinePayment::AuthorizeNet;
-    my $card = Business::CreditCard::Object->new( $argshash->{cc} );
+    $logger->debug(
+        "applying payment via processor '" . $argshash->{processor} . "'"
+    );
 
-    $logger->debug("applying authorize.net payment");
+    # Find B:OP constructor arguments specific to our payment processor.
+    my %bop_args = get_bop_args_filler($argshash);
 
-    if ( ! $card->is_valid ) {
-        $logger->warn("authorize.net card number is invalid");
+    # We're assuming that all B:OP processors accept this argument to the
+    # contstructor.
+    $bop_args{test_transaction} = $argshash->{testmode};
 
-        return {
-            statusText       => "should return address:(patron_id):",
-            processor        => $argshash->{processor},
-            testmode         => $argshash->{testmode},
-            card             => $card->number(),
-            expiration       => $argshash->{expiration},
-            name             => $patron->first_given_name,
-            patron_id        => $patron->id,
-            patron_patron_id => $patron->mailing_address,
-            statusCode       => 500
-        };
-    }
-
-    my $type = $card->type();
-
-    if ( substr( $type, -5, 5 ) =~ / card/ ) {
-        $type = substr( $type, 0, -5 );
-    }
-
-    my $transaction = new Business::OnlinePayment( 
-        $argshash->{processor}, 'test_transaction' => $argshash->{testmode});
-
-    $transaction->content(
-        type        => "$type", #'American Express', 'VISA', 'MasterCard'
-        login       => $argshash->{login},
-        password    => $argshash->{password},
-        action      => $argshash->{action},
-        description => $argshash->{description},
-        amount      => $argshash->{amount},
-        card_number => $card->number(),
-        expiration  => $argshash->{expiration},
-        cvv2        => $argshash->{cvv2},
-        first_name  => $patron->first_given_name,
-        last_name   => $patron->family_name,
-        address     => $patron->mailing_address->street1,
-        city        => $patron->mailing_address->city,
-        state       => $patron->mailing_address->state,
-        zip         => $patron->mailing_address->post_code,
-        customer_id => $patron->id
+    my $transaction = new Business::OnlinePayment(
+        $argshash->{processor}, %bop_args
     );
 
+    $transaction->content(prepare_bop_content($argshash, $patron, $cardtype));
     $transaction->submit();
 
-    if ( $transaction->is_success() ) {
-        $logger->info("authorize.net payment succeeded");
-        return {
-            statusText => "Card approved: "
-              . $transaction->authorization,
-            statusCode      => 200,
-            approvalCode    => $transaction->authorization,
+    # The data structures that we return based on success or failure are still
+    # basically from earlier code.  These might should be improved/reduced.
+    if ($transaction->is_success()) {
+        $logger->info($argshash->{processor} . " payment succeeded");
+
+        my $retval = {
+            statusText => "Transaction approved: " . $transaction->authorization,
+            statusCode => 200,
+            approvalCode => $transaction->authorization,
             server_response => $transaction->server_response
-
         };
 
-    } else {
-        $logger->info("authorize.net card declined");
+        # These result fields may be important in PayPal xactions? Not sure.
+        foreach (qw/correlationid avs_code cvv2_code/) {
+            if ($transaction->can($_)) {
+                $retval->{$_} = $transaction->$_;
+            }
+        }
+        return $retval;
+    }
+    else {
+        $logger->info($argshash->{processor} . " payment failed");
         return {
-            statusText => "Card decliined: " . $transaction->error_message,
-            statusCode      => 500,
-            approvalCode    => $transaction->error_message,
+            statusText => "Transaction declined: " . $transaction->error_message,
+            statusCode => 500,
+            errorMessage => $transaction->error_message,
             server_response => $transaction->server_response
         };
     }
+
 }
 
 
@@ -232,7 +257,6 @@
     signature => {
         desc   => q/Returns the total amount of the patron can pay via credit card/,
         params => [
-            { desc => 'Authentication token',      type => 'string' },
             { desc => 'Authentication token', type => 'string' },
             { desc => 'User id', type => 'number' }
         ],
@@ -267,16 +291,15 @@
     });
 
     my %hash;
-    my @orgs;
     for my $org ( @$circ_orgs, @$groc_orgs ) {
         my $o = $org->{billing_location};
         $o = $org->{circ_lib} unless $o;
-        next if $hash{$org};
+        next if $hash{$o};    # was $hash{$org}, but that doesn't make sense.  $org is a hashref and $o gets added in the next line.
         $hash{$o} = $U->ou_ancestor_setting_value($o, 'global.credit.allow', $e);
     }
 
     my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
-    $logger->debug("credit: relevent orgs that allow credit payments => @credit_orgs");
+    $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
 
     my $xact_summaries =
       OpenILS::Application::AppUtils->simplereq('open-ils.actor',

Modified: trunk/Open-ILS/src/support-scripts/oils_header.pl
===================================================================
--- trunk/Open-ILS/src/support-scripts/oils_header.pl	2009-10-08 15:51:51 UTC (rev 14308)
+++ trunk/Open-ILS/src/support-scripts/oils_header.pl	2009-10-08 17:23:01 UTC (rev 14309)
@@ -21,7 +21,7 @@
 
 
 # Some useful objects
-our $cache = "OpenSRF::Utils::Cache";
+our $cache    = "OpenSRF::Utils::Cache";
 our $apputils = "OpenILS::Application::AppUtils";
 our $memcache;
 our $user;
@@ -29,23 +29,23 @@
 our $authtime;
 
 # Some constants for our services
-our $AUTH		= 'open-ils.auth';
-our $STORAGE	= 'open-ils.storage';
-our $SEARCH		= 'open-ils.search';
-our $CIRC		= 'open-ils.circ';
-our $CAT			= 'open-ils.cat';
-our $MATH		= 'opensrf.math';
-our $SETTINGS	= 'opensrf.settings';
-our $ACTOR		= 'open-ils.actor';
+our $AUTH     = 'open-ils.auth';
+our $STORAGE  = 'open-ils.storage';
+our $SEARCH   = 'open-ils.search';
+our $CIRC     = 'open-ils.circ';
+our $CAT      = 'open-ils.cat';
+our $MATH     = 'opensrf.math';
+our $SETTINGS = 'opensrf.settings';
+our $ACTOR    = 'open-ils.actor';
 
-sub AUTH		{ return $AUTH; }
-sub STORAGE { return $STORAGE; }
-sub SEARCH	{ return $SEARCH; }
-sub CIRC		{ return $CIRC; }
-sub CAT		{ return $CAT; }
-sub MATH		{ return $MATH; }
+sub AUTH     { return $AUTH;     }
+sub STORAGE  { return $STORAGE;  }
+sub SEARCH   { return $SEARCH;   }
+sub CIRC     { return $CIRC;     }
+sub CAT      { return $CAT;      }
+sub MATH     { return $MATH;     }
 sub SETTINGS { return $SETTINGS; }
-sub ACTOR	{ return $ACTOR; }
+sub ACTOR    { return $ACTOR;    }
 
 
 #----------------------------------------------------------------

Modified: trunk/Open-ILS/src/support-scripts/test-scripts/notes.pl
===================================================================
--- trunk/Open-ILS/src/support-scripts/test-scripts/notes.pl	2009-10-08 15:51:51 UTC (rev 14308)
+++ trunk/Open-ILS/src/support-scripts/test-scripts/notes.pl	2009-10-08 17:23:01 UTC (rev 14309)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 require '../oils_header.pl';
-use vars qw/ $user $authtoken /;
+use vars qw/ $user $authtoken /;    # FIXME: $user not used?
 use strict; use warnings;
 use Time::HiRes qw/time/;
 use Data::Dumper;
@@ -12,12 +12,12 @@
 
 err("usage: $0 <config> <username> <password> <patronid> <title> <text>") unless $ARGV[5];
 
-my $config		= shift; # - bootstrap config
-my $username	= shift; # - oils login username
-my $password	= shift; # - oils login password
-my $patronid	= shift;
-my $title		= shift;
-my $text			= shift;
+my $config   = shift;  # - bootstrap config
+my $username = shift;  # - oils login username
+my $password = shift;  # - oils login password
+my $patronid = shift;
+my $title    = shift;
+my $text     = shift;
 
 
 sub go {

Added: trunk/Open-ILS/src/support-scripts/test-scripts/payment_test.pl
===================================================================
--- trunk/Open-ILS/src/support-scripts/test-scripts/payment_test.pl	                        (rev 0)
+++ trunk/Open-ILS/src/support-scripts/test-scripts/payment_test.pl	2009-10-08 17:23:01 UTC (rev 14309)
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+
+#----------------------------------------------------------------
+# Simple example
+#----------------------------------------------------------------
+
+require '../oils_header.pl';
+use strict; use warnings;
+
+use Getopt::Long;
+
+sub usage {
+    return <<END_OF_USAGE;
+$0 [-h] --login=UserName --password==MyPass [OPTIONS] [Transaction data]
+
+Required Arguments:
+    -l --login      Assigned by your processor API (specified in -t)
+    -p --password   Assigned by your processor API (specified in -t)
+
+Options:
+    -t --target       Payment processor (default PayPal)
+    -s --signature    A "long password" required by PayPal in leiu of certificates
+    -r --server       Use a specific server with a processor (AuthorizeNet)
+    -c --config_file  opensrf_core.xml file (default /openils/conf/opensrf_core.xml)
+
+Transaction data:
+    -a --amount    Monetary value, no dollar sign, default a random value under 25.00
+    -i --id        Patron ID#, default 5 (for no reason)
+    -n --number    Credit card number to be charged
+    -x --expires   Date (MM-YYYY) of card expiration, default 12-2014
+
+Example:
+
+$0  --login=seller_1254418209_biz_api1.esilibrary.com \\
+    --password=1254618222 \\
+    --signature=AiPC9xjkCyDFQXbSkoZcgqH3hpacAVPVw5GcZgNKVA9SGKcbrqLuhLks \\
+    --amount=32.75 \\
+    --id=13042
+
+END_OF_USAGE
+}
+
+### DEFAULTS
+my $config    = '/openils/conf/opensrf_core.xml';
+my $processor = 'PayPal';
+my $number    = '4123000011112228';
+my $expires   = '12-2014';
+my $id        = 5;
+
+### Empties
+my ($login, $password, $signature, $help, $amount, $server);
+
+GetOptions(
+    'config_file=s' => \$config,
+    'target=s'      => \$processor,
+    'login=s'       => \$login,
+    'password=s'    => \$password,
+    's|signature=s' => \$signature,
+    'amount=f'      => \$amount,
+    'id=i'          => \$id,
+    'number=s'      => \$number,
+    'x|expires=s'   => \$expires,
+    'r|server=s'    => \$server,
+    'help|?'        => \$help,
+);
+
+$help and print usage and exit;
+
+unless ($login and $processor and $password) {
+    print usage;
+    exit;
+}
+osrf_connect($config);
+
+$amount or $amount = int(rand(25)) . '.' . sprintf("%02d", int(rand(99)));
+
+print <<END_OF_DUMP;
+Attempting transaction:
+\{
+    processor => $processor,
+        login => $login,
+     password => $password,
+    signature => $signature,
+       amount => $amount,
+           cc => $number,
+   expiration => $expires,
+       server => $server,
+     testmode => 1,
+    patron_id => $id,
+      country => US,
+  description => test transaction processid $$
+\}
+
+END_OF_DUMP
+
+my( $user, $evt ) = simplereq('open-ils.credit', 'open-ils.credit.process', 
+{
+    processor => $processor,
+        login => $login,
+     password => $password,
+    signature => $signature,
+       amount => $amount,
+           cc => $number,
+   expiration => $expires,
+       server => $server,
+     testmode => 1,
+    patron_id => $id,
+      country => "US",
+  description => "test transaction processid $$"
+}
+);
+oils_event_die($evt); # this user was not found / not all methods return events..
+print debug($user);
+



More information about the open-ils-commits mailing list