[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