From: senator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4> Date: Tue, 1 Jun 2010 20:35:21 +0000 (+0000) Subject: Move credit card processing ML code into the circ module, removing API method X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=d33e6522c0d01dcadcc0d73384a193941b16487a;p=evergreen%2Fpines.git Move credit card processing ML code into the circ module, removing API method for direct payment processing. (Using open-ils.circ.money.payment with auth can invoke CC processing). git-svn-id: svn://svn.open-ils.org/ILS/trunk@16550 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm index 16bd62ef13..794dba3542 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm @@ -8,6 +8,7 @@ use OpenILS::Application::Circ::Survey; use OpenILS::Application::Circ::StatCat; use OpenILS::Application::Circ::Holds; use OpenILS::Application::Circ::HoldNotify; +use OpenILS::Application::Circ::CreditCard; use OpenILS::Application::Circ::Money; use OpenILS::Application::Circ::NonCat; use OpenILS::Application::Circ::CopyLocations; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/CreditCard.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/CreditCard.pm new file mode 100644 index 0000000000..1475fc64c4 --- /dev/null +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/CreditCard.pm @@ -0,0 +1,295 @@ +# -------------------------------------------------------------------- +# Copyright (C) 2008 Niles Ingalls +# Niles Ingalls <nilesi@zionsville.lib.in.us> +# Bill Erickson <erickson@esilibrary.com> +# Joe Atzberger <jatzberger@esilibrary.com> +# Lebbeous Fogle-Weekley <lebbeous@esilibrary.com> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# -------------------------------------------------------------------- +package OpenILS::Application::Circ::CreditCard; +use base qw/OpenSRF::Application/; +use strict; use warnings; + +use Business::CreditCard; +use Business::OnlinePayment; +use Locale::Country; + +use OpenILS::Event; +use OpenSRF::Utils::Logger qw/:logger/; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Application::AppUtils; +my $U = "OpenILS::Application::AppUtils"; + +use constant CREDIT_NS => "credit"; + +# 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} + ); +} + +sub get_processor_settings { + my $org_unit = shift; + my $processor = lc shift; + + +{ map { ($_ => + $U->ou_ancestor_setting_value( + $org_unit, CREDIT_NS . ".processor.${processor}.${_}" + )) } qw/enabled login password signature server testmode/ + }; +} + +# signature => { +# desc => 'Process a payment via a supported processor (AuthorizeNet, Paypal)', +# params => [ +# { desc => q/Hash of arguments with these keys: +# patron_id: Not a barcode, but a patron's internal ID +# ou: Org unit where transaction happens +# processor: Payment processor to use (AuthorizeNet, PayPal, etc) +# cc: credit card number +# cvv2: 3 or 4 digits from back of card +# amount: transaction value +# action: optional (default: Normal Authorization) +# 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 + street2) +# 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 +# /, type => 'hash' } +# ], +# return => { desc => 'Hash of status information', type =>'hash' } +# } + +sub process_payment { + my ($argshash) = @_; + + # Confirm some required arguments. + return OpenILS::Event->new('BAD_PARAMS') + unless $argshash + and $argshash->{cc} + and $argshash->{amount} + and $argshash->{expiration} + and $argshash->{ou}; + + if (!$argshash->{processor}) { + if (!($argshash->{processor} = + $U->ou_ancestor_setting_value( + $argshash->{ou}, CREDIT_NS . '.processor.default'))) { + return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_SPECIFIED'); + } + } + # Basic sanity check on processor name. + if ($argshash->{processor} !~ /^[a-z0-9_\-]+$/i) { + return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_ALLOWED'); + } + + # Get org unit settings related to our processor + my $psettings = get_processor_settings( + $argshash->{ou}, $argshash->{processor} + ); + + if (!$psettings->{enabled}) { + return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_ENABLED'); + } + + # Add the org unit settings for the chosen processor to our argshash. + $argshash = +{ %{$argshash}, %{$psettings} }; + + # At least the following (derived from org unit settings) are required. + return OpenILS::Event->new('CREDIT_PROCESSOR_BAD_PARAMS') + unless $argshash->{login} + and $argshash->{password}; + + # A valid patron_id is also required. + my $e = new_editor(); + my $patron = $e->retrieve_actor_user( + [ + $argshash->{patron_id}, + { + flesh => 1, + flesh_fields => { au => ["mailing_address"] } + } + ] + ) or return $e->event; + + return dispatch($argshash, $patron); +} + +sub prepare_bop_content { + my ($argshash, $patron, $cardtype) = @_; + + 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; + + $content{FirstName} = $content{first_name}; # kludge mcugly for PP + $content{LastName} = $content{last_name}; + + + # 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{address} .= ", " . $patron->mailing_address->street2 + if $patron->mailing_address->street2; + + $content{city} ||= $patron->mailing_address->city; + $content{state} ||= $patron->mailing_address->state; + $content{zip} ||= $patron->mailing_address->post_code; + $content{country} ||= $patron->mailing_address->country; + + # Yet another fantastic kludge. country2code() comes from Locale::Country. + # PayPal must have 2 letter country field (ISO 3166) that's uppercase. + if (length($content{country}) > 2 && $argshash->{processor} eq 'PayPal') { + $content{country} = uc country2code($content{country}); + } + + %content; +} + +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"); + + # 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 => "Credit card number invalid", + statusCode => 500 + }; + } + + # 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//; + + $logger->debug( + "applying payment via processor '" . $argshash->{processor} . "'" + ); + + # Find B:OP constructor arguments specific to our payment processor. + my %bop_args = get_bop_args_filler($argshash); + + # We're assuming that all B:OP processors accept this argument to the + # contstructor. + $bop_args{test_transaction} = $argshash->{testmode}; + + my $transaction = new Business::OnlinePayment( + $argshash->{processor}, %bop_args + ); + + $transaction->content(prepare_bop_content($argshash, $patron, $cardtype)); + $transaction->submit(); + + # 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, + processor => $argshash->{processor}, + cardType => $cardtype, + statusCode => 200, + approvalCode => $transaction->authorization, + server_response => $transaction->server_response + }; + + # 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 => "Transaction declined: " . $transaction->error_message, + statusCode => 500, + errorMessage => $transaction->error_message, + server_response => $transaction->server_response + }; + } + +} + + +1; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm index dbf9cdc699..2dccec39b6 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm @@ -65,7 +65,36 @@ __PACKAGE__->register_method( ], }/, type => 'hash' }, - ] + ], + "return" => { + "desc" => + q{1 on success, event on failure. Event possibilities include: + BAD_PARAMS + (Bad parameters were given to this API method itself. + See note field.) + CREDIT_PROCESSOR_NOT_SPECIFIED + (Evergreen has not been set up to process CC payments) + CREDIT_PROCESSOR_NOT_ALLOWED + (Evergreen has been incorrectly setup for CC payments) + CREDIT_PROCESSOR_NOT_ENABLED + (Evergreen has been set up for CC payments, but an admin + has not explicitly enabled them) + CREDIT_PROCESSOR_BAD_PARAMS + (Evergreen has been incorrectly setup for CC payments; + specifically, the login and/or password for the CC + processor weren't provided) + CREDIT_PROCESSOR_DECLINED_TRANSACTION + (We contacted the CC processor to attempt the charge, but + they declined it. See the statusText field for their + message.) + CREDIT_PROCESSOR_SUCCESS_WO_RECORD + (A payment was processed successfully, but couldn't be + recorded in Evergreen. This is bad bad bad, as it means + somebody made a payment but isn't getting credit for it. + See note field for more info.) +}, + "type" => "number" + } } ); sub make_payments { @@ -201,10 +230,8 @@ sub make_payments { if ($cc_args->{where_process} == 1) { return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number') if not $cc_args->{number}; - my $response = $apputils->simplereq( - 'open-ils.credit', - 'open-ils.credit.process', - { + my $response = + OpenILS::Application::Circ::CreditCard::process_payment({ "desc" => $cc_args->{note}, "amount" => $total_paid, "patron_id" => $user_id, @@ -221,10 +248,13 @@ sub make_payments { "city" => $cc_args->{billing_city}, "state" => $cc_args->{billing_state}, "zip" => $cc_args->{billing_zip}, - } - ); + }); if (exists $response->{ilsevent}) { + $logger->info( + "event response from process_payment(): " . + $response->{"textcode"} + ); return $response; } if ($response->{statusCode} != 200) { diff --git a/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm b/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm deleted file mode 100644 index 10d779c2d5..0000000000 --- a/Open-ILS/src/perlmods/OpenILS/Application/CreditCard.pm +++ /dev/null @@ -1,299 +0,0 @@ -# -------------------------------------------------------------------- -# Copyright (C) 2008 Niles Ingalls -# Niles Ingalls <nilesi@zionsville.lib.in.us> -# Bill Erickson <erickson@esilibrary.com> -# Joe Atzberger <jatzberger@esilibrary.com> -# Lebbeous Fogle-Weekley <lebbeous@esilibrary.com> -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -------------------------------------------------------------------- -package OpenILS::Application::CreditCard; -use base qw/OpenSRF::Application/; -use strict; use warnings; - -use Business::CreditCard; -use Business::OnlinePayment; -use Locale::Country; - -use OpenILS::Event; -use OpenSRF::Utils::Logger qw/:logger/; -use OpenILS::Utils::CStoreEditor qw/:funcs/; -use OpenILS::Application::AppUtils; -my $U = "OpenILS::Application::AppUtils"; - -use constant CREDIT_NS => "credit"; - -# 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} - ); -} - -sub get_processor_settings { - my $org_unit = shift; - my $processor = lc shift; - - +{ map { ($_ => - $U->ou_ancestor_setting_value( - $org_unit, CREDIT_NS . ".processor.${processor}.${_}" - )) } qw/enabled login password signature server testmode/ - }; -} - -__PACKAGE__->register_method( - method => 'process_payment', - api_name => 'open-ils.credit.process', - signature => { - desc => 'Process a payment via a supported processor (AuthorizeNet, Paypal)', - params => [ - { desc => q/Hash of arguments with these keys: - patron_id: Not a barcode, but a patron's internal ID - ou: Org unit where transaction happens - processor: Payment processor to use (AuthorizeNet, PayPal, etc) - cc: credit card number - cvv2: 3 or 4 digits from back of card - amount: transaction value - action: optional (default: Normal Authorization) - 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 + street2) - 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 - /, type => 'hash' } - ], - return => { desc => 'Hash of status information', type =>'hash' } - } -); - -sub process_payment { - my ($self, $client, $argshash) = @_; # $client is unused in this sub - - # Confirm some required arguments. - return OpenILS::Event->new('BAD_PARAMS') - unless $argshash - and $argshash->{cc} - and $argshash->{amount} - and $argshash->{expiration} - and $argshash->{ou}; - - if (!$argshash->{processor}) { - if (!($argshash->{processor} = - $U->ou_ancestor_setting_value( - $argshash->{ou}, CREDIT_NS . '.processor.default'))) { - return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_SPECIFIED'); - } - } - # Basic sanity check on processor name. - if ($argshash->{processor} !~ /^[a-z0-9_\-]+$/i) { - return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_ALLOWED'); - } - - # Get org unit settings related to our processor - my $psettings = get_processor_settings( - $argshash->{ou}, $argshash->{processor} - ); - - if (!$psettings->{enabled}) { - return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_ENABLED'); - } - - # Add the org unit settings for the chosen processor to our argshash. - $argshash = +{ %{$argshash}, %{$psettings} }; - - # At least the following (derived from org unit settings) are required. - return OpenILS::Event->new('CREDIT_PROCESSOR_BAD_PARAMS') - unless $argshash->{login} - and $argshash->{password}; - - # A valid patron_id is also required. - my $e = new_editor(); - my $patron = $e->retrieve_actor_user( - [ - $argshash->{patron_id}, - { - flesh => 1, - flesh_fields => { au => ["mailing_address"] } - } - ] - ) or return $e->event; - - return dispatch($argshash, $patron); -} - -sub prepare_bop_content { - my ($argshash, $patron, $cardtype) = @_; - - 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; - - $content{FirstName} = $content{first_name}; # kludge mcugly for PP - $content{LastName} = $content{last_name}; - - - # 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{address} .= ", " . $patron->mailing_address->street2 - if $patron->mailing_address->street2; - - $content{city} ||= $patron->mailing_address->city; - $content{state} ||= $patron->mailing_address->state; - $content{zip} ||= $patron->mailing_address->post_code; - $content{country} ||= $patron->mailing_address->country; - - # Yet another fantastic kludge. country2code() comes from Locale::Country. - # PayPal must have 2 letter country field (ISO 3166) that's uppercase. - if (length($content{country}) > 2 && $argshash->{processor} eq 'PayPal') { - $content{country} = uc country2code($content{country}); - } - - %content; -} - -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"); - - # 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 => "Credit card number invalid", - statusCode => 500 - }; - } - - # 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//; - - $logger->debug( - "applying payment via processor '" . $argshash->{processor} . "'" - ); - - # Find B:OP constructor arguments specific to our payment processor. - my %bop_args = get_bop_args_filler($argshash); - - # We're assuming that all B:OP processors accept this argument to the - # contstructor. - $bop_args{test_transaction} = $argshash->{testmode}; - - my $transaction = new Business::OnlinePayment( - $argshash->{processor}, %bop_args - ); - - $transaction->content(prepare_bop_content($argshash, $patron, $cardtype)); - $transaction->submit(); - - # 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, - processor => $argshash->{processor}, - cardType => $cardtype, - statusCode => 200, - approvalCode => $transaction->authorization, - server_response => $transaction->server_response - }; - - # 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 => "Transaction declined: " . $transaction->error_message, - statusCode => 500, - errorMessage => $transaction->error_message, - server_response => $transaction->server_response - }; - } - -} - - -1;