Business-OnlinePayment-3.02/0000755000175000017500000000000011623567756014507 5ustar ivanivanBusiness-OnlinePayment-3.02/Changes0000644000175000017500000001357411623567716016010 0ustar ivanivanRevision history for Perl extension Business::OnlinePayment. 3.02 Fri Aug 19 16:20:04 PDT 2011 - Fix fatal error calling ->info('supported_actions') on a gateway that does not yet support introspection (e.g. AuthorizeNet) - Fix introspection with a complicated supported_actions - Documentation fix for recurring_billing flag - Add optional transaction field to documentation: currency - Fix spelling mistake in preCharge.pm POD, thanks to gregor herrmann, Closes: CPAN#69647 3.01 Wed Jul 14 13:54:57 PDT 2010 - Add optional transaction fields to documentation: tax, freight, duty, tax_exempt, po_number. - Add return fields to documentation: order_number, avs_code, cvv2_response, response_code, response_headers, response_page. - Add beginning of introspection interface for processor modules. - Add electronic check fields to documentation: bank_city, bank_state - Add clarification to notes_for_module_writers_v3 on authorization vs. order_number - (3.01_03) Add ECHECK_void_requires_account to introspection - (3.01_04) Refactor most of the B:OP:HTTPS code out to Net:HTTPS::Any - (3.01_04) Add card_token documentation. Add token_support to introspection. 3.00 Mon Aug 17 15:55:11 PDT 2009 - It finally happened. - doc: add repo info 3.00_09 Mon Jul 21 20:44:08 PDT 2008 - doc: Allowable values of account_type - doc: Recurring billing actions and fields - doc: new standard fields track1, track2, patch from Chris Travers, thanks! - B:OP:HTTPS: Normalize https_get and https_post response_code to "NNN message" without HTTP version even when using Net::SSLeay. 3.00_08 Wed Jun 13 17:51:14 PDT 2007 - B:OP:HTTPS: set response_page, response_code, response_headers - B:OP:HTTPS: Normalize https_post (and debugging) response_code to "NNN message" even when using LWP/Crypt::SSLeay. - B:OP: defined &$class is not how you detect if a class has been loaded (just using use should be fine). Closes: CPAN#22071 - Enable retrieval of fraud transaction score and transaction ID, B:OP and B:FD:preCharge patch from Jason Hall, thanks! 3.00_07 Fri Mar 23 14:54:57 PDT 2007 - B:OP:HTTPS request headers now work with Crypt::SSLeay too. 3.00_06 Tue Mar 13 12:26:04 PDT 2007 - B:OP:HTTPS: add optional \%options (options hashref) to https_get and https_post to allow modules using this to set headers, etc. required for PayflowPro HTTP protocol support - B:OP:HTTPS: support setting Net::SSLeay "$mime_type6" argument - B:OP:HTTPS: pass $DEBUG value to $Net::SSLeay::trace (debug control) - new() now passes %data (processor data) to set_defaults - update B:OP:HTTPS to allow setting request headers - doc: list a bunch of previously undocumented fields 3.00_05 29 Nov 2006 - Update Makefile.PL so Business::FraudDetect::preCharge is installed, included by make dist, etc. - Phil Lobbes is responsible for the rest of the work in this release. Thanks! - Reworked _pre_submit functionality: - Add Class data %WrappedSubmitClassMethod to remember "wrapped" submit - Fix new() to check %WrappedSubmitClassMethod to avoid creating deep recursion - Used feedback from MSCHWERN / Bug #22074 to cleanup new/_pre_submit more - Now always wrap submit() method with _pre_submit() (but only once) - no longer populate _child_submit, code in anon sub was cleaned up - use return values from _pre_submit to determine if real submit is called. the return values from _pre_submit should be reviewed/verified still - _risk_detect(): explicity set return value - _pre_submit(): explicity set return value - Updated tests: - 8 new tests and 1 new "MOCK3" driver for testing _pre_submit() functionality - test for new() replacing subclass submit and causing deep recursion - minor cleanup of test drivers - fix "submit unchanged" test case: now wrapped with _pre_submit() always - Updated POD documentation: - Minor documentation change in print statements use ',' instead of '.' 3.00_04 Tue Oct 10 12:49:43 PDT 2006 - failure statues (see notes_for_module_writers_v3) - oops, forgot _03 changelog in _03 - B:OP:HTTPS: require Net::SSLeay 1.30 and remove _my_https_post kludge - eliminate warnings about redefined subroutines - Business::FraudDetect and Business::FraudDetect::preCharge included - From Phil Lobbes: - content() now returns empty hash if no content is defined - build_subs now uses can() so it doesn't redefine subs (and generate warnings) - DOC: SYNOPSIS now uses correct "card_number" and not "cardnumber" - created test cases for (all?) methods except _risk_detect _pre_submit - Removed some old commented out 2.x statements - set $VERSION using eval per modperlstyle - new() now will strip off multiple leading dashes before calling build_subs NOTE: we should probably have it remove any \W to make perl happy - required_fields() now croaks with a list of missing fields - Lots of general cleanup, no functional changes. "fill paragraphs" in POD, remove extra whitespace, sorted %fields, dump_contents now sorts %contents, added myself to AUTHORS. - From Frederic Briere (closes: CPAN#21082): - DOC: s/exp_date/expiration/ 3.00_03 Wed Mar 16 02:41:59 PST 2005 - https_post now accepts a scalar of raw content instead of key value pairs 3.00_02 Mon Jan 10 21:36:53 PST 2005 - HTTPS base class now has https_post in addition to https_get 3.00_01 Thu Aug 26 04:49:26 2004 - first of the v3 dev releases 0.01 Sun Jul 25 13:59:10 1999 - original version; created by h2xs 1.19 Business-OnlinePayment-3.02/Makefile.PL0000644000175000017500000000120111376654313016442 0ustar ivanivanuse ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( 'NAME' => 'Business::OnlinePayment', 'VERSION_FROM' => 'OnlinePayment.pm', # finds $VERSION 'AUTHOR' => 'Ivan Kohler ', 'PMLIBDIRS' => [ 'OnlinePayment', 'FraudDetect' ], 'NORECURS' => 1, # dont descend into subdirectories 'PREREQ_PM' => { 'Tie::IxHash' => 0, 'Net::HTTPS::Any' => 0, }, #'dist' => {CI => 'ci -l'}, ); Business-OnlinePayment-3.02/FraudDetect.pm0000644000175000017500000000552310634073643017230 0ustar ivanivanpackage Business::FraudDetect; use vars qw / $VERSION @ISA /; $VERSION = '0.01'; @ISA = qw / Business::OnlinePayment /; 1; =pod =head1 NAME Business::FraudDetect - A cohort to Business::OnlinePayment =head1 SYNOPSIS my %processor_info = ( fraud_detection => 'preCharge', maximum_fraud_score => 500, preCharge_id => '1000000000000001', preCharge_security1 => 'abcdef0123', preCharge_security2 => '3210fedcba', ) my $transaction = new Business::OnlinePayment($processor, %processor_info); $transaction->content( type => 'Visa', amount => '49.95', cardnumber => '1234123412341238', expiration => '0100', name => 'John Q Doe', ); $transaction->submit(); if($transaction->is_success()) { print "Card processed successfully: ".$transaction->authorization()."\n"; } else { print "Card was rejected: ".$transaction->error_message()."\n"; } =head1 DESCRIPTION This is a module that adds functionality to Business::OnlinePayment. See L. The user instantiates a Business::OnlinePayment object per usual, adding in three processor directives =over 4 =item * fraud_detection Which Fraud Detection module to use. =item * maximum_fraud_score FraudDetection drivers are expected to return a numeric "risk" factor, this parameter allows you to set the threshold to reject the transaction based on that risk. Higher numbers are "riskier" transactions. =item * other driver-specific parameters. Consult the specific Fraud Detection module you intend to use for its required parameters. =back The $tx->submit() method is overridden to interpose a FraudDetection phase. A subordinate object is created using the same content as the parent OnlinePayment object, and a I action is run against that subordinate object. If the resulting fraud score is less than or equal to the maximum_risk parameter, the parent transaction will be allowed to proceed. Otherwise, a failure state will exist with a suitable error message. =head1 METHODS This module provides no new methods. It does, however override the submit method to interpose an additional Fraud Detection phase. =head1 AUTHORS Original author: Lawrence Statton Current maintainer: Ivan Kohler as part of the Business::OnlinePayment distribution. =head1 DISCLAIMER THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. =head1 SEE ALSO L, http://420.am/business-onlinepayment =cut Business-OnlinePayment-3.02/OnlinePayment.pm0000644000175000017500000004500111623567627017624 0ustar ivanivanpackage Business::OnlinePayment; use strict; use vars qw($VERSION %_info_handler); use Carp; require 5.005; $VERSION = '3.02'; $VERSION = eval $VERSION; # modperlstyle: convert the string into a number # Remember subclasses we have "wrapped" submit() with _pre_submit() my %Presubmit_Added = (); my @methods = qw( authorization order_number error_message failure_status fraud_detect is_success maximum_risk path port require_avs result_code server server_response test_transaction transaction_type fraud_score fraud_transaction_id response_code response_header response_page ); #fallback sub _info { my $class = shift; ( my $gw = $class ) =~ s/^Business::OnlinePayment:://; { 'info_compat' => '0.00', 'gateway_name' => $gw, 'module_notes' => "Module does not yet provide info.", }; } #allow classes to declare info in a flexible way, but return normalized info %_info_handler = ( 'supported_types' => sub { my( $class, $v ) = @_; my $types = ref($v) ? $v : defined($v) ? [ $v ] : []; $types = { map { $_=>1 } @$types } if ref($types) eq 'ARRAY'; $types; }, 'supported_actions' => sub { my( $class, $v ) = @_; return %$v if ref($v) eq 'HASH'; $v = [ $v ] unless ref($v); my $types = $class->info('supported_types') || {}; ( map { $_ => $v } keys %$types ); }, ); sub info { my $class = shift; #class or object my $info = $class->_info; if ( @_ ) { my $key = shift; exists($_info_handler{$key}) ? &{ $_info_handler{$key} }( $class, $info->{$key} ) : $info->{$key}; } else { wantarray ? ( keys %$info ) : [ keys %$info ]; } } sub new { my($class,$processor,%data) = @_; croak("unspecified processor") unless $processor; my $subclass = "${class}::$processor"; eval "use $subclass"; croak("unknown processor $processor ($@)") if $@; my $self = bless {processor => $processor}, $subclass; $self->build_subs(@methods); if($self->can("set_defaults")) { $self->set_defaults(%data); } foreach(keys %data) { my $key = lc($_); my $value = $data{$_}; $key =~ s/^\-+//; $self->build_subs($key); $self->$key($value); } # "wrap" submit with _pre_submit only once unless ( $Presubmit_Added{$subclass} ) { my $real_submit = $subclass->can('submit'); no warnings 'redefine'; no strict 'refs'; *{"${subclass}::submit"} = sub { my $self = shift; return unless $self->_pre_submit(@_); return $real_submit->($self, @_); } } return $self; } sub _risk_detect { my ($self, $risk_transaction) = @_; my %parent_content = $self->content(); $parent_content{action} = 'Fraud Detect'; $risk_transaction->content( %parent_content ); $risk_transaction->submit(); if ($risk_transaction->is_success()) { $self->fraud_score( $risk_transaction->fraud_score ); $self->fraud_transaction_id( $risk_transaction->fraud_transaction_id ); if ( $risk_transaction->fraud_score <= $self->maximum_fraud_score()) { return 1; } else { $self->error_message('Excessive risk from risk management'); } } else { $self->error_message('Error in risk detection stage: ' . $risk_transaction->error_message); } $self->is_success(0); return 0; } my @Fraud_Class_Path = qw(Business::OnlinePayment Business::FraudDetect); sub _pre_submit { my ($self) = @_; my $fraud_detection = $self->fraud_detect(); # early return if user does not want optional risk mgt return 1 unless $fraud_detection; # Search for an appropriate FD module foreach my $fraud_class ( @Fraud_Class_Path ) { my $subclass = $fraud_class . "::" . $fraud_detection; eval "use $subclass ()"; if ($@) { croak("error loading fraud_detection module ($@)") unless ( $@ =~ m/^Can\'t locate/ ); } else { my $risk_tx = bless( { processor => $fraud_detection }, $subclass ); $risk_tx->build_subs(@methods); if ($risk_tx->can('set_defaults')) { $risk_tx->set_defaults(); } $risk_tx->_glean_parameters_from_parent($self); return $self->_risk_detect($risk_tx); } } croak("Unable to locate fraud_detection module $fraud_detection" . " in \@INC under Fraud_Class_Path (\@Fraud_Class_Path" . " contains: @Fraud_Class_Path) (\@INC contains: @INC)"); } sub content { my($self,%params) = @_; if(%params) { if($params{'type'}) { $self->transaction_type($params{'type'}); } %{$self->{'_content'}} = %params; } return exists $self->{'_content'} ? %{$self->{'_content'}} : (); } sub required_fields { my($self,@fields) = @_; my @missing; my %content = $self->content(); foreach(@fields) { push(@missing, $_) unless exists $content{$_}; } croak("missing required field(s): " . join(", ", @missing) . "\n") if(@missing); } sub get_fields { my($self, @fields) = @_; my %content = $self->content(); #my %new = (); #foreach(@fields) { $new{$_} = $content{$_}; } #return %new; map { $_ => $content{$_} } grep defined $content{$_}, @fields; } sub remap_fields { my($self,%map) = @_; my %content = $self->content(); foreach( keys %map ) { $content{$map{$_}} = $content{$_}; } $self->content(%content); } sub submit { my($self) = @_; croak("Processor subclass did not override submit function"); } sub dump_contents { my($self) = @_; my %content = $self->content(); my $dump = ""; foreach(sort keys %content) { $dump .= "$_ = $content{$_}\n"; } return $dump; } # didnt use AUTOLOAD because Net::SSLeay::AUTOLOAD passes right to # AutoLoader::AUTOLOAD, instead of passing up the chain sub build_subs { my $self = shift; foreach(@_) { next if($self->can($_)); eval "sub $_ { my \$self = shift; if(\@_) { \$self->{$_} = shift; } return \$self->{$_}; }"; } } #helper method sub silly_bool { my( $self, $value ) = @_; return 1 if $value =~ /^[yt]/i; return 0 if $value =~ /^[fn]/i; #return 1 if $value == 1; #return 0 if $value == 0; $value; #die?? } 1; __END__ =head1 NAME Business::OnlinePayment - Perl extension for online payment processing =head1 SYNOPSIS use Business::OnlinePayment; my $transaction = new Business::OnlinePayment($processor, %processor_info); $transaction->content( type => 'Visa', amount => '49.95', card_number => '1234123412341238', expiration => '0100', name => 'John Q Doe', ); $transaction->submit(); if($transaction->is_success()) { print "Card processed successfully: ", $transaction->authorization(), "\n"; } else { print "Card was rejected: ", $transaction->error_message(), "\n"; } =head1 DESCRIPTION Business::OnlinePayment is a generic module for processing payments through online credit card processors, electronic cash systems, etc. =head1 CONSTRUCTOR =head2 new($processor, %processor_options) Create a new Business::OnlinePayment object, $processor is required, and defines the online processor to use. If necessary, processor options can be specified, currently supported options are 'Server', 'Port', and 'Path', which specify how to find the online processor (https://server:port/path), but individual processor modules should supply reasonable defaults for this information, override the defaults only if absolutely necessary (especially path), as the processor module was probably written with a specific target script in mind. =head1 TRANSACTION SETUP METHODS =head2 content(%content) The information necessary for the transaction, this tends to vary a little depending on the processor, so we have chosen to use a system which defines specific fields in the frontend which get mapped to the correct fields in the backend. The currently defined fields are: =head3 PROCESSOR FIELDS =over 4 =item login Your login name to use for authentication to the online processor. =item password Your password to use for authentication to the online processor. =back =head3 REQUIRED TRANSACTION FIELDS =over 4 =item type Transaction type, supported types are: CC (credit card), ECHECK (electronic check) and LEC (phone bill billing). Deprecated types are: Visa, MasterCard, American Express, Discover, Check. Not all processors support all transaction types. =item action What action being taken by this transaction. Currently available are: =over 8 =item Normal Authorization =item Authorization Only =item Post Authorization =item Void =item Credit =item Recurring Authorization =item Modify Recurring Authorization =item Cancel Recurring Authorization =back =item amount The amount of the transaction. No dollar signs or currency identifiers, just a whole or floating point number (i.e. 26, 26.1 or 26.13). =back =head3 OPTIONAL TRANSACTION FIELDS =over 4 =item description A description of the transaction (used by some processors to send information to the client, normally not a required field). =item invoice_number An invoice number, for your use and not normally required, many processors require this field to be a numeric only field. =item po_number Purchase order number (normally not required). =item tax Tax amount (portion of amount field, not added to it). =item freight Freight amount (portion of amount field, not added to it). =item duty Duty amount (portion of amount field, not added to it). =item tax_exempt Tax exempt flag (i.e. TRUE, FALSE, T, F, YES, NO, Y, N, 1, 0). =item currency Currency, specified as an ISO 4217 three-letter code, such as USD, CAD, EUR, AUD, DKK, GBP, JPY, NZD, etc. =back =head3 CUSTOMER INFO FIELDS =over 4 =item customer_id A customer identifier, again not normally required. =item name The customer's name, your processor may not require this. =item first_name =item last_name The customer's first and last name as separate fields. =item company The customer's company name, not normally required. =item address The customer's address (your processor may not require this unless you are requiring AVS Verification). =item city The customer's city (your processor may not require this unless you are requiring AVS Verification). =item state The customer's state (your processor may not require this unless you are requiring AVS Verification). =item zip The customer's zip code (your processor may not require this unless you are requiring AVS Verification). =item country Customer's country. =item ship_first_name =item ship_last_name =item ship_company =item ship_address =item ship_city =item ship_state =item ship_zip =item ship_country These shipping address fields may be accepted by your processor. Refer to the description for the corresponding non-ship field for general information on each field. =item phone Customer's phone number. =item fax Customer's fax number. =item email Customer's email address. =item customer_ip IP Address from which the transaction originated. =back =head3 CREDIT CARD FIELDS =over 4 =item card_number Credit card number. =item expiration Credit card expiration. =item cvv2 CVV2 number (also called CVC2 or CID) is a three- or four-digit security code used to reduce credit card fraud. =item card_token If supported by your gateway, you can pass a card_token instead of a card_number and expiration. =cut #=item card_response # #Some card_token schemes implement a challenge/response handshake. In those #cases, this field is used for the response. In most cases the handshake #it taken care of by the gateway module. =item track1 Track 1 on the magnetic stripe (Card present only) =item track2 Track 2 on the magnetic stripe (Card present only) =item recurring_billing Recurring billing flag =back =head3 ELECTRONIC CHECK FIELDS =over 4 =item account_number Bank account number =item routing_code Bank's routing code =item account_type Account type. Can be (case-insensitive): B, B, B or B. =item account_name Account holder's name. =item bank_name Bank name. =item bank_city Bank city. =item bank_state Bank state. =item check_type Check type. =item customer_org Customer organization type. =item customer_ssn Customer's social security number. =item license_num Customer's driver's license number. =item license_dob Customer's date of birth. =back =head3 RECURRING BILLING FIELDS =over 4 =item interval Interval expresses the amount of time between billings: digits, whitespace and units (currently "days" or "months" in either singular or plural form). =item start The date of the first transaction (used for processors which allow delayed start) expressed as YYYY-MM-DD. =item periods The number of cycles of interval length for which billing should occur (inclusive of 'trial periods' if the processor supports recurring billing at more than one rate) =back =head2 test_transaction() Most processors provide a test mode, where submitted transactions will not actually be charged or added to your batch, calling this function with a true argument will turn that mode on if the processor supports it, or generate a fatal error if the processor does not support a test mode (which is probably better than accidentally making real charges). =head2 require_avs() Providing a true argument to this module will turn on address verification (if the processor supports it). =head1 TRANSACTION SUBMISSION METHOD =head2 submit() Submit the transaction to the processor for completion =head1 TRANSACTION RESULT METHODS =head2 is_success() Returns true if the transaction was submitted successfully, false if it failed (or undef if it has not been submitted yet). =head2 error_message() If the transaction has been submitted but was not accepted, this function will return the provided error message (if any) that the processor returned. =head2 failure_status() If the transaction failed, it can optionally return a specific failure status (normalized, not gateway-specific). Currently defined statuses are: "expired", "nsf" (non-sufficient funds), "stolen", "pickup", "blacklisted" and "declined" (card/transaction declines only, not other errors). Note that (as of Aug 2006) this is only supported by some of the newest processor modules, and that, even if supported, a failure status is an entirely optional field that is only set for specific kinds of failures. =head2 authorization() If the transaction has been submitted and accepted, this function will provide you with the authorization code that the processor returned. Store this if you would like to run inquiries or refunds on the transaction later. =head2 order_number() The unique order number for the transaction generated by the gateway. Store this if you would like to run inquiries or refunds on the transaction later. =head2 card_token() If supported by your gateway, a card_token can be used in a subsequent transaction to refer to a card number. =head2 fraud_score() Retrieve or change the fraud score from any Business::FraudDetect plugin =head2 fraud_transaction_id() Retrieve or change the transaction id from any Business::FraudDetect plugin =head2 response_code() =head2 response_headers() =head2 response_page() These three fields are set by some processors (especially those which use HTTPS) when the transaction fails at the communication level rather than as a transaction. response_code is the HTTP response code and message, i.e. '500 Internal Server Error'. response_headers is a hash reference of the response headers response_page is the raw content. =head2 result_code() Returns the precise result code that the processor returned, these are normally one letter codes that don't mean much unless you understand the protocol they speak, you probably don't need this, but it's there just in case. =head2 avs_code() =head2 cvv2_response() =head1 MISCELLANEOUS INTERNAL METHODS =head2 transaction_type() Retrieve the transaction type (the 'type' argument to contents()). Generally only used internally, but provided in case it is useful. =head2 server() Retrieve or change the processor submission server address (CHANGE AT YOUR OWN RISK). =head2 port() Retrieve or change the processor submission port (CHANGE AT YOUR OWN RISK). =head2 path() Retrieve or change the processor submission path (CHANGE AT YOUR OWN RISK). =head1 HELPER METHODS FOR GATEWAY MODULE AUTHORS =head2 build_subs( @sub_names ) Build setter/getter subroutines for new return values. =head2 get_fields( @fields ) Get the named fields if they are defined. =head2 remap_fields( %map ) Remap field content (and stuff it back into content). =head2 required_fields( @fields ) Croaks if any of the required fields are not present. =head2 dump_contents =head2 silly_bool( $value ) Returns 0 if the value starts with y, Y, t or T. Returns 1 if the value starts with n, N, f or F. Otherwise returns the value itself. Use this for handling boolean content like tax_exempt. =head1 AUTHORS (v2 series) Jason Kohles, email@jasonkohles.com (v3 rewrite) Ivan Kohler Phil Lobbes Ephil at perkpartners dot comE =head1 COPYRIGHT Copyright (c) 1999-2004 Jason Kohles Copyright (c) 2004 Ivan Kohler Copyright (c) 2007-2011 Freeside Internet Services, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 HOMEPAGE Homepage: http://420.am/business-onlinepayment/ Development: http://420.am/business-onlinepayment/ng.html =head1 MAILING LIST Please direct current development questions, patches, etc. to the mailing list: http://420.am/cgi-bin/mailman/listinfo/bop-devel/ =head1 REPOSITORY The code is available from our public CVS repository: export CVSROOT=":pserver:anonymous@cvs.freeside.biz:/home/cvs/cvsroot" cvs login # The password for the user `anonymous' is `anonymous'. cvs checkout Business-OnlinePayment Or on the web: http://freeside.biz/cgi-bin/viewvc.cgi/Business-OnlinePayment/ Many (but by no means all!) processor plugins are also available in the same repository, see: http://freeside.biz/cgi-bin/viewvc.cgi/ =head1 DISCLAIMER THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. =head1 SEE ALSO http://420.am/business-onlinepayment/ For verification of credit card checksums, see L. =cut Business-OnlinePayment-3.02/notes_for_module_writers_v30000644000175000017500000001116211376654313022154 0ustar ivanivanThese are the module writer's notes for v3. See the regular "notes_for_module_writers" file first. - If your gateway is HTTPS-based, use (or convert to) Business::OnlinePayment::HTTPS !! - Handling failures: - If your processor module encounters a setup problem, communication error or other problem that's prevents the card from even being run, you should die (or croak) with a useful error message. Setting is_success to 0 and returning normally should only be done when the transaction *processing* was sucessful (or at least elicited some sort of result from the gateway), but the transaction itself returned a "normal" decline status of some sort. - (NEW IN 3.00_04) You should set "failure_status" depending on the specific failure result, if (and only if) the failure results from one of the defined statuses: - "expired" - "nsf" (non-sufficient funds / credit limit) - "stolen" - "pickup" - "blacklisted" - "inactive" (inactive card or not authorized for card-not-present) (?) - "decline" (other card/transaction declines only, not other errors) You should use code like this so your module can work with B:OP versions before 3.00_04: $self->build_subs('failure_status') unless $self->can('failure_status'); (or add "failure_status" to your build_subs call if you have one during initialization) - (NEW IN 3.01) Introspection: - Add an _info subroutine to your module that returns a hashref of information: sub _info { { 'info_compat' => '0.01', # always 0.01 for now, # 0.02 will have requirements 'gateway_name' => 'Example Gateway', 'gateway_url' => 'http://www.example.com/', 'module_version' => $VERSION, 'supported_types' => [ qw( CC ECHECK ) ], 'token_support' => 0, #card storage/tokenization support 'test_transaction' => 0, #set true if ->test_transaction(1) works 'supported_actions' => [ 'Normal Authorization', 'Authorization Only', 'Post Authorization', 'Void', 'Credit', ], }; } # or a more complicated case: sub _info { { 'info_compat' => '0.01', # always 0.01 for now, # 0.02 will have requirements 'gateway_name' => 'Example Gateway', 'gateway_url' => 'http://www.example.com/', 'module_version' => $VERSION, 'module_notes' => 'usage notes', 'supported_types' => [ qw( CC ECHECK ) ], 'token_support' => 1, 'test_transaction' => 1, 'supported_actions' => { 'CC' => [ 'Normal Authorization', 'Authorization Only', 'Post Authorization', 'Void', 'Credit', 'Recurring Authorization', 'Modify Recurring Authorization', 'Cancel Recurring Authorization', ], 'ECHECK' => [ 'Normal Authorization', 'Void', 'Credit', ], }, 'CC_void_requires_card' => 1, 'ECHECK_void_requires_account' => 1, #routing_code, account_number, name }; } - authorization and order_number (NEWLY DOCUMENTED IN 3.01): Gateways will return one or two values from Authorization Only and Normal Authorization transactions that must be submitted back with a Post Authorization, Void, or Credit transaction. If the gateway returns one value, return this as "authorization" If the gateway returns two values, return one as "authorization" and the other as "order_number". Typically "authorization" is the more low-level value returned from the underlying processing network while "order_number" is a unique tranaction id generated by the gateway. Business-OnlinePayment-3.02/TODO0000644000175000017500000000065311277656075015201 0ustar ivanivan- do something about state_id / state_id_state (linkpoint, freeside) vs. license_num / license_dob (authorize.net, docs here). wtf! - Use Net::HTTPS::Any - hmm... requires HTTP::Headers v1.59 - move revmap_fields into the base class - remap_fields: Not the cleanest thing to mix up our namespace with the processor-specific names. - Define a better model of exception handling that doesn't involve dying on some things. Business-OnlinePayment-3.02/notes_for_module_writers0000644000175000017500000000617310464204615021543 0ustar ivanivanInformation on creating a new processor backend to go with Business::OnlinePayment. ----------------------------------------------------------- NOTE: These are the old v2 notes. If you're writing a new module, see these first and then read notes_for_module_writers_v3 If you're updating an existing module for v3, go directly to notes_for_module_writers_v3 Create a subclass of Business::OnlinePayment called Business::OnlinePayment::(processor name). You should override at least the following functions (look at Business::OnlinePayment::AuthorizeNet as an example). set_defaults: set default information for server/port/path (if your processor is not web based you probably dont need to override these). submit: This is the only function you _MUST_ override, the superclass function merely dies(), so if you dont override this, your module doesnt do anything. Some of the things that submit should do: * Turn the data into a format usable by the online processor, some convenience functions (remap_fields and get_fields), are provided by the superclass to make this a little easier. * Check and make sure that all the required fields have been filled in (the superclass provides the function required_fields() which can do this for you). * IMPORTANT: If the superclass function test_transaction() returns true, the transaction must be submitted to the processor in test mode. If your processor is unable to easily provide a test mode, your module should die() if test_transaction() returns true, this prevents accidental charges for people who thought they were submitting test transactions. * If your processor provides an option of whether or not you want address verification, your module should check to see if the require_avs() function returns true, and turn on AVS checking if it does. * Submit the transaction to the processor and collect a response. * Do the following with the response: * call server_response() with a copy of the entire unprocessed response, to be stored in case the user needs it in the future. * call is_success() with either a true or false value, indicating if the transaction was successful or not. * call result_code() with the servers result code (this is generally one field from the response indicating that it was successful or a failure, most processors provide many possible result codes to differentiate different types of success and failure). * If the transaction was successful, call authorization() with the authorization code the processor provided. * If the transaction was not successful, call error_message() with either the processor provided error message, or some error message to indicate why it failed. Business-OnlinePayment-3.02/FraudDetect/0000755000175000017500000000000011623567756016701 5ustar ivanivanBusiness-OnlinePayment-3.02/FraudDetect/preCharge.pm0000644000175000017500000001673311612056136021130 0ustar ivanivanpackage Business::FraudDetect::preCharge; use strict; use Carp; use vars qw($VERSION @ISA); use Business::OnlinePayment::HTTPS; @ISA = qw( Business::OnlinePayment::HTTPS ); $VERSION = '0.02'; sub _glean_parameters_from_parent { my ($self, $parent) = @_; foreach my $method (qw / precharge_id precharge_security1 precharge_security2 /) { $self->$method($parent->$method); } } sub set_defaults { my ($self) = @_; $self->server('api.precharge.net'); $self->port(443); $self->path('/charge'); $self->build_subs(qw /currency fraud_score error_code precharge_id precharge_security1 precharge_security2 force_success fraud_transaction_id / ); $self->currency('USD'); return $self; } sub submit { my ($self) = @_; if ($self->force_success()) { $self->is_success(1); $self->result_code('1'); $self->error_message('No Error. Force success path'); return $self; } my %content = $self->content(); Carp::croak("Action: $content{action} not supported.") unless lc($content{action}) eq 'fraud detect'; $self->required_fields(qw( amount card_number expiration first_name last_name state zip country phone email ip_address )); $self->remap_fields( qw/ ip_address ecom_billto_online_ip zip ecom_billto_postal_postalcode phone ecom_billto_telecom_phone_number first_name ecom_billto_postal_name_first last_name ecom_billto_postal_name_last email ecom_billto_online_email country ecom_billto_postal_countrycode card_number ecom_payment_card_number amount ecom_transaction_amount / ); my %post_data = $self->get_fields(qw( ecom_billto_online_ip ecom_billto_postal_postalcode ecom_billto_telecom_phone_number ecom_billto_online_email ecom_transaction_amount currency ecom_billto_postal_name_first ecom_billto_postal_name_last ecom_billto_postal_countrycode ecom_payment_card_number )); # set up some reasonable defaults # # split out MM/YY from exp date # @post_data{ qw/ ecom_payment_card_expdate_month ecom_payment_card_expdate_year / } = split(/\//,$content{expiration}); @post_data{qw/merchant_id security_1 security_2/} = ( $self->precharge_id, $self->precharge_security1, $self->precharge_security2 ); if ($self->test_transaction()) { $post_data{test} = 1; } my ($page, $response, %headers) = $self->https_post(\%post_data); $self->server_response($page); my @details = split ',',$page; my %error_map = ( 101 => 'Invalid Request Method', 102 => 'Invalid Request URL', 103 => 'Invalid Security Code(s)', 104 => 'Merchant Status not Verified', 105 => 'Merchant Feed is Disabled', 106 => 'Invalid Request Type', 107 => 'Missing IP Address', 108 => 'Invalid IP Address Syntax', 109 => 'Missing First Name', 110 => 'Invalid First Name', 111 => 'Missing Last Name', 112 => 'Invalid Last Name', 113 => 'Invalid Address 1', 114 => 'Invalid Address 2', 115 => 'Invalid City', 116 => 'Invalid State', 117 => 'Invalid Country', 118 => 'Missing Postal Code', 119 => 'Invalid Postal Code', 120 => 'Missing Phone Number', 121 => 'Invalid Phone Number', 122 => 'Missing Expiration Month', 123 => 'Invalid Expiration Month', 124 => 'Missing Expiration Year', 125 => 'Invalid Expiration Year', 126 => 'Expired Credit Card', 127 => 'Missing Credit Card Number', 128 => 'Invalid Credit Card Number', 129 => 'Missing Email Address', 130 => 'Invlaid Email Syntax', 131 => 'Duplicate Transaction', 132 => 'Invlaid Transaction Amount', 133 => 'Invalid Currency', 998 => 'Unknown Error', 999 => 'Service Unavailable', 1001 => 'No detail returned', ); my %output = ( error => 1001 ); foreach my $detail (@details) { my ($k, $v) = split('=', $detail); $output{$k} = $v; } if ($output{response} == 1 ) { $self->is_success(1); $self->fraud_score($output{score}); $self->result_code($output{response}); $self->fraud_transaction_id($output{transaction}); $self->error_message('No Error. Risk assesment transaction successful'); } else { $self->is_success(0); $self->fraud_score($output{score}); $self->result_code($output{error}); $self->error_message( exists( $error_map{$output{error}} ) ? $error_map{$output{error}} : "preCharge error $output{error} occurred." ); } } 1; =pod =head1 NAME Business::FraudDetect::preCharge - backend for Business::FraudDetect (part of Business::OnlinePayment) =head1 SYNOPSIS use Business::OnlinePayment my $tx = new Business::OnlinePayment ( 'someGateway', fraud_detect => 'preCharge', maximum_fraud_score => 500, preCharge_id => '1000000000000001', preCharge_security1 => 'abcdef0123', preCharge_security2 => '3210fedcba', ); $tx->content( first_name => 'Larry Walton', last_name => 'Sanders', login => 'testdrive', password => '', action => 'Normal Authorization', type => 'VISA', state => 'MA', zip => '02145', country => 'US', phone => '617 555 8900', email => 'lws@sanders.com', ip_address => '18.62.0.6', card_number => '4111111111111111', expiration => '0307', amount => '25.00', ); $tx->submit(); if ($tx->is_success()) { # successful charge my $score = $tx->fraud_score; my $id = $tx->fraud_transaction_id; #returns the preCharge transaction id } else { # unsucessful my $score = $tx->fraud_score; } =head1 DESCRIPTION This module provides a driver for the preCharge Risk Management Solutions API Version 1.7 (16 Jan 2006). See L and L for more information. =head1 CONSTRUCTION Whe constructing the Business::OnlinePayment object, three risk management parameters must be included for the preCharge object to be properly constructed. =over 4 =item * precharge_id This field is called "merchant_id" in the preCharge API manual =item * precharge_security1 This field is called "security_1" in the preCharge API manual =item * precharge_secuirty2 This field is called "security_2" in the preCharge API manual =back =head1 METHODS This module provides no public methods. =head1 AUTHORS Lawrence Statton Jason Hall =head1 DISCLAIMER THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. =head1 SEE ALSO http://420.am/business-onlinepayment =cut Business-OnlinePayment-3.02/META.yml0000644000175000017500000000107711623567756015765 0ustar ivanivan--- #YAML:1.0 name: Business-OnlinePayment version: 3.02 abstract: ~ author: - Ivan Kohler license: unknown distribution_type: module configure_requires: ExtUtils::MakeMaker: 0 build_requires: ExtUtils::MakeMaker: 0 requires: Net::HTTPS::Any: 0 Tie::IxHash: 0 no_index: directory: - t - inc generated_by: ExtUtils::MakeMaker version 6.56 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 Business-OnlinePayment-3.02/MANIFEST0000644000175000017500000000051111614606747015627 0ustar ivanivanREADME Changes MANIFEST Makefile.PL OnlinePayment.pm OnlinePayment/HTTPS.pm FraudDetect.pm FraudDetect/preCharge.pm t/00load.t t/bop_https.t t/bop.t t/fd_precharge.t t/pod.t t/introspection.t notes_for_module_writers notes_for_module_writers_v3 META.yml Module meta-data (added by MakeMaker) TODO Business-OnlinePayment-3.02/README0000644000175000017500000000146511417422267015361 0ustar ivanivanCopyright (c) 1999-2004 Jason Kohles Copyright (c) 2004 Ivan Kohler Copyright (c) 2007-2010 Freeside Internet Services, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Business::OnlinePayment is a generic interface for processing payments through online credit card processors, online check acceptance houses, etc. (If you like buzzwords, call it an "multiplatform ecommerce-enabling middleware solution"). IMPORTANT: Business::OnlinePayment only defines the frontend interface to the system, in order to use it you also need to have at least one backend processing module installed. Homepage: http://420.am/business-onlinepayment/ Search CPAN for backends: http://search.cpan.org/search?m=all&q=Business::OnlinePayment:: Business-OnlinePayment-3.02/t/0000755000175000017500000000000011623567756014752 5ustar ivanivanBusiness-OnlinePayment-3.02/t/fd_precharge.t0000644000175000017500000000226710552212613017532 0ustar ivanivan#!/usr/bin/perl use strict; use warnings; use Test::More; { # fake test driver (with a submit method) package Business::OnlinePayment::MOCK; use strict; use warnings; use base qw(Business::OnlinePayment); sub submit { my $self = shift; return 1; } } $INC{"Business/OnlinePayment/MOCK.pm"} = "testing"; use Business::OnlinePayment; my $package = "Business::OnlinePayment"; my $fddrv = "preCharge"; eval { my $tobj = $package->new("MOCK"); $tobj->fraud_detect($fddrv); $tobj->submit; }; if ( $@ =~ /One of Net::SSLeay.*?or Crypt::SSLeay/ ) { plan skip_all => "fraud_detect: $@\n"; } else { plan tests => 5; } my $obj = $package->new("MOCK"); can_ok( $obj, qw(fraud_detect) ); # fraud detection failure modes my $fdbog = "__BOGUS_PROCESSOR"; is( $obj->fraud_detect($fdbog), $fdbog, "fraud_detect set to '$fdbog'" ); eval { $obj->submit; }; like( $@, qr/^Unable to locate fraud_detection /, "fraud_detect with unknown processor croaks" ); is( $obj->fraud_detect($fddrv), $fddrv, "fraud_detect set to '$fddrv'" ); eval { $obj->submit; }; like( $@, qr/^missing required /, "fraud_detect($fddrv) missing fields" ); # XXX: more test cases needed Business-OnlinePayment-3.02/t/bop_https.t0000644000175000017500000000041610552214172017117 0ustar ivanivan#!/usr/bin/perl use strict; use warnings; use Test::More; my $package = "Business::OnlinePayment::HTTPS"; eval "use $package;"; # HTTPS support is optional plan( $@ ? ( skip_all => "$package: $@\n" ) : ( tests => 1 ) ); can_ok( $package, qw(https_get https_post) ); Business-OnlinePayment-3.02/t/pod.t0000644000175000017500000000025510554167321015705 0ustar ivanivan#!/usr/bin/perl use strict; use warnings; use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); Business-OnlinePayment-3.02/t/00load.t0000644000175000017500000000026610554167201016201 0ustar ivanivan#!/usr/bin/perl use strict; use warnings; use Test::More tests => 1; BEGIN { use_ok("Business::OnlinePayment") or BAIL_OUT("unable to load Business::OnlinePayment\n"); } Business-OnlinePayment-3.02/t/bop.t0000644000175000017500000001513710554167235015714 0ustar ivanivan#!/usr/bin/perl use strict; use warnings; use Test::More tests => 57; use Business::OnlinePayment; { # fake test driver 1 (no submit method) package Business::OnlinePayment::MOCK1; use strict; use warnings; use base qw(Business::OnlinePayment); } { # fake test driver 2 (with submit method that dies) package Business::OnlinePayment::MOCK2; use base qw(Business::OnlinePayment::MOCK1); sub submit { my $self = shift; die("in processor submit\n"); } } { # fake test driver 3 (with submit method) package Business::OnlinePayment::MOCK3; use base qw(Business::OnlinePayment::MOCK1); sub submit { my $self = shift; return 1; } } my $package = "Business::OnlinePayment"; my @drivers = qw(MOCK1 MOCK2 MOCK3); my $driver = $drivers[0]; # trick to make use() happy (called in Business::OnlinePayment->new) foreach my $drv (@drivers) { $INC{"Business/OnlinePayment/${drv}.pm"} = "testing"; } { # new can_ok( $package, qw(new) ); my $obj; eval { $obj = $package->new(); }; like( $@, qr/^unspecified processor/, "new() without a processor croaks" ); eval { $obj = $package->new("__BOP BOGUS PROCESSOR__"); }; like( $@, qr/^unknown processor/, "new() with an unknown processor croaks" ); $obj = $package->new($driver); isa_ok( $obj, $package ); isa_ok( $obj, $package . "::" . $driver ); # build_subs(%fields) can_ok( $obj, qw( authorization error_message failure_status fraud_detect is_success maximum_risk path port require_avs result_code server server_response test_transaction transaction_type ) ); # new (via build_subs) automatically creates accessors for arguments $obj = $package->new( $driver, "proc1" => "value1" ); can_ok( $package, "proc1" ); can_ok( $obj, "proc1" ); # new (via build_subs) automatically creates accessors for arguments $obj = $package->new( $driver, qw(key1 v1 Key2 v2 -Key3 v3 --KEY4 v4) ); can_ok( $package, qw(key1 key2 key3 key4) ); can_ok( $obj, qw(key1 key2 key3 key4) ); # new makes all accessors lowercase and removes leading dash(es) is( $obj->key1, "v1", "value of key1 (method key1) is v1" ); is( $obj->key2, "v2", "value of Key2 (method key2) is v2" ); is( $obj->key3, "v3", "value of -Key3 (method key3) is v3" ); is( $obj->key4, "v4", "value of --KEY4 (method key4) is v4" ); } # XXX # { # _risk_detect } { # _pre_submit my $s_orig = Business::OnlinePayment::MOCK3->can("submit"); is( ref $s_orig, "CODE", "MOCK3 submit code ref $s_orig" ); # test to ensure we do not go recursive when wrapping submit my $obj1 = $package->new("MOCK3"); my $s_new1 = $obj1->can("submit"); isnt( $s_new1, $s_orig, "MOCK3 submit code ref $s_new1 (wrapped)" ); is( $obj1->submit, "1", "MOCK3(obj1) submit returns 1" ); my $obj2 = $package->new("MOCK3"); my $s_new2 = $obj2->can("submit"); is( $obj2->submit, "1", "MOCK3(obj2) submit returns 1" ); } { # content my $obj; $obj = $package->new($driver); can_ok( $package, qw(content) ); can_ok( $obj, qw(content) ); is( $obj->content, (), "default content is empty" ); my %data = qw(k1 v1 type test -k2 v2 K3 v3); is_deeply( { $obj->content(%data) }, \%data, "content is set properly" ); is( $obj->transaction_type, "test", "content sets transaction_type" ); %data = ( type => undef ); is_deeply( { $obj->content(%data) }, \%data, "content with type=>undef" ); is( $obj->transaction_type, "test", "transaction_type not reset" ); } { # required_fields my $obj = $package->new($driver); can_ok( $package, qw(required_fields) ); can_ok( $obj, qw(required_fields) ); is( $obj->required_fields, 0, "no required fields" ); eval { $obj->required_fields("field1"); }; like( $@, qr/^missing required field/, "missing required_fields croaks" ); } { # get_fields my $obj = $package->new($driver); can_ok( $package, qw(get_fields) ); can_ok( $obj, qw(get_fields) ); my %data = ( a => 1, b => 2, c => undef, d => 4 ); $obj->content(%data); my ( @want, %get ); @want = qw(a b); %get = map { $_ => $data{$_} } @want; is_deeply( { $obj->get_fields(@want) }, \%get, "get_fields with defined vals" ); @want = qw(a c d); %get = map { defined $data{$_} ? ( $_ => $data{$_} ) : () } @want; is_deeply( { $obj->get_fields(@want) }, \%get, "get_fields does not get fields with undef values" ); } { # remap_fields my $obj = $package->new($driver); can_ok( $package, qw(remap_fields) ); can_ok( $obj, qw(remap_fields) ); my %data = ( a => 1, b => 2, c => undef, d => 4 ); $obj->content(%data); my %map = ( a => "Aa", d => "Dd" ); my %get = ( a => 1, Aa => 1, b => 2, c => undef, d => 4, Dd => 4 ); $obj->remap_fields(%map); is_deeply( { $obj->content }, \%get, "remap_fields" ); } { # submit my $obj = $package->new($driver); can_ok( $package, qw(submit) ); can_ok( $obj, qw(submit) ); eval { $obj->submit; }; like( $@, qr/^Processor subclass did not /, "missing submit() croaks" ); isnt( $obj->can("submit"), $package->can("submit"), "submit changed" ); my $mock2 = $package->new("MOCK2"); can_ok( $mock2, qw(submit) ); isnt( $mock2->can("submit"), $package->can("submit"), "submit changed" ); eval { $mock2->submit; }; like( $@, qr/^in processor submit/, "processor submit() is called" ); } { # dump_contents my $obj = $package->new($driver); can_ok( $package, qw(dump_contents) ); can_ok( $obj, qw(dump_contents) ); } { # build_subs my $obj; $obj = $package->new($driver); can_ok( $package, qw(build_subs) ); can_ok( $obj, qw(build_subs) ); # build_subs creates accessors for arguments my %data = qw(key1 v1 Key2 v2 -Key3 v3 --KEY4 v4); my @subs = sort { lc( ( $a =~ /(\w+)/ )[0] ) cmp lc( ( $b =~ /(\w+)/ )[0] ) } keys %data; $obj->build_subs(@subs); # perl does not allow dashes ("-") in subroutine names foreach my $sub (@subs) { if ( $sub !~ /^\w+/ ) { is( ref $package->can($sub), "", "$package can NOT $sub" ); is( ref $obj->can($sub), "", ref($obj) . " can NOT $sub" ); } else { can_ok( $package, $sub ); can_ok( $obj, $sub ); $obj->$sub( $data{$sub} ); is( $obj->$sub, $data{$sub}, "$sub accessor returns $data{$sub}" ); } } } Business-OnlinePayment-3.02/t/introspection.t0000644000175000017500000001024611614610011020007 0ustar ivanivan#!/usr/bin/perl use strict; use warnings; use Test::More tests => 13; { # fake test driver 1 (no _info hash) package Business::OnlinePayment::MOCK1; use strict; use warnings; use base qw(Business::OnlinePayment); } { # fake test driver 2 (with _info hash) package Business::OnlinePayment::MOCK2; use base qw(Business::OnlinePayment::MOCK1); sub _info { { 'info_compat' => '0.01', # always 0.01 for now, # 0.02 will have requirements 'gateway_name' => 'Example Gateway', 'gateway_url' => 'http://www.example.com/', 'module_version' => '0.01', #$VERSION, 'supported_types' => [ qw( CC ECHECK ) ], 'token_support' => 0, #card storage/tokenization support 'test_transaction' => 0, #set true if ->test_transaction(1) works 'supported_actions' => [ 'Normal Authorization', 'Authorization Only', 'Post Authorization', 'Void', 'Credit', ], 'CC_void_requires_card' => 1, }; } } { # fake test driver 3 (with _info hash) package Business::OnlinePayment::MOCK3; use base qw(Business::OnlinePayment::MOCK1); sub _info { { 'info_compat' => '0.01', # always 0.01 for now, # 0.02 will have requirements 'gateway_name' => 'Example Gateway', 'gateway_url' => 'http://www.example.com/', 'module_version' => '0.01', #$VERSION, 'supported_types' => [ qw( CC ECHECK ) ], 'token_support' => 1, 'test_transaction' => 1, 'supported_actions' => { 'CC' => [ 'Normal Authorization', 'Authorization Only', 'Post Authorization', 'Void', 'Credit', 'Recurring Authorization', 'Modify Recurring Authorization', 'Cancel Recurring Authorization', ], 'ECHECK' => [ 'Normal Authorization', 'Void', 'Credit', ], }, }; } } my $package = "Business::OnlinePayment"; my @drivers = qw(MOCK1 MOCK2 MOCK3); my $driver = $drivers[0]; # trick to make use() happy (called in Business::OnlinePayment->new) foreach my $drv (@drivers) { $INC{"Business/OnlinePayment/${drv}.pm"} = "testing"; } my $obj = $package->new($driver); isa_ok( $obj, $package ); isa_ok( $obj, $package . "::" . $driver ); my %throwaway_actions = eval { $obj->info('supported_actions') }; ok( !$@, "->info('supported_actions') works w/o gateway module introspection"); $driver = 'MOCK2'; $obj = $package->new($driver); isa_ok( $obj, $package ); isa_ok( $obj, $package . "::" . $driver ); my %actions = eval { $obj->info('supported_actions') }; ok( grep { $_ eq 'Void' } @{ $actions{$_} }, "->info('supported_actions') works w/gateway module introspection ($_)" ) foreach qw( CC ECHECK ); ok($obj->info('CC_void_requires_card'), 'CC_void_requires_card introspection'); ok(!$obj->info('ECHECK_void_requires_account'), 'ECHECK_void_requires_account introspection'); $driver = 'MOCK3'; $obj = $package->new($driver); isa_ok( $obj, $package ); isa_ok( $obj, $package . "::" . $driver ); %actions = eval { $obj->info('supported_actions') }; ok( grep { $_ eq 'Authorization Only' } @{ $actions{CC} }, "->info('supported_actions') w/hashref supported_actions"); ok( ! grep { $_ eq 'Authorization Only' } @{ $actions{ECHECK} }, "->info('supported_actions') w/hashref supported_actions"); Business-OnlinePayment-3.02/OnlinePayment/0000755000175000017500000000000011623567756017271 5ustar ivanivanBusiness-OnlinePayment-3.02/OnlinePayment/HTTPS.pm0000644000175000017500000001073011376654313020521 0ustar ivanivanpackage Business::OnlinePayment::HTTPS; use strict; use base qw(Business::OnlinePayment); use vars qw($VERSION $DEBUG); use Tie::IxHash; use Net::HTTPS::Any 0.10; $VERSION = '0.10'; $DEBUG = 0; =head1 NAME Business::OnlinePayment::HTTPS - Base class for HTTPS payment APIs =head1 SYNOPSIS package Business::OnlinePayment::MyProcessor; use base qw(Business::OnlinePayment::HTTPS); sub submit { my $self = shift; #... # pass a list (order is preserved, if your gateway needs that) ( $page, $response, %reply_headers ) = $self->https_get( field => 'value', ... ); # or a hashref my %hash = ( field => 'value', ... ); ( $page, $response_code, %reply_headers ) = $self->https_get( \%hash ); #... } =head1 DESCRIPTION This is a base class for HTTPS based gateways, providing useful code for implementors of HTTPS payment APIs. It depends on Net::HTTPS::Any, which in turn depends on Net::SSLeay _or_ ( Crypt::SSLeay and LWP::UserAgent ). =head1 METHODS =over 4 =item https_get [ \%options ] HASHREF | FIELD => VALUE, ... Accepts parameters as either a hashref or a list of fields and values. In the latter case, ordering is preserved (see L to do so when passing a hashref). Returns a list consisting of the page content as a string, the HTTP response code and message (i.e. "200 OK" or "404 Not Found"), and a list of key/value pairs representing the HTTP response headers. The options hashref supports setting headers: { headers => { 'X-Header1' => 'value', ... }, } =cut # Content-Type => 'text/namevalue', sub https_get { my $self = shift; # handle optional options hashref my $opts = {}; if ( scalar(@_) > 1 and ref( $_[0] ) eq "HASH" ) { $opts = shift; } # accept a hashref or a list (keep it ordered) my $post_data; if ( ref( $_[0] ) eq 'HASH' ) { $post_data = shift; } elsif ( scalar(@_) > 1 ) { tie my %hash, 'Tie::IxHash', @_; $post_data = \%hash; } elsif ( scalar(@_) == 1 ) { $post_data = shift; } else { die "https_get called with no params\n"; } $self->build_subs(qw( response_page response_code response_headers )); my( $res_page, $res_code, @res_headers) = Net::HTTPS::Any::https_get( 'host' => $self->server, 'path' => $self->path, 'headers' => $opts->{headers}, 'args' => $post_data, 'debug' => $DEBUG, ); $self->response_page( $res_page ); $self->response_code( $res_code ); $self->response_headers( { @res_headers } ); ( $res_page, $res_code, @res_headers ); } =item https_post [ \%options ] SCALAR | HASHREF | FIELD => VALUE, ... Accepts form fields and values as either a hashref or a list. In the latter case, ordering is preserved (see L to do so when passing a hashref). Also accepts instead a simple scalar containing the raw content. Returns a list consisting of the page content as a string, the HTTP response code and message (i.e. "200 OK" or "404 Not Found"), and a list of key/value pairs representing the HTTP response headers. The options hashref supports setting headers and Content-Type: { headers => { 'X-Header1' => 'value', ... }, Content-Type => 'text/namevalue', } =cut sub https_post { my $self = shift; # handle optional options hashref my $opts = {}; if ( scalar(@_) > 1 and ref( $_[0] ) eq "HASH" ) { $opts = shift; } my %post = ( 'host' => $self->server, 'path' => $self->path, 'headers' => $opts->{headers}, 'Content-Type' => $opts->{'Content-Type'}, 'debug' => $DEBUG, ); # accept a hashref or a list (keep it ordered) my $post_data = ''; my $content = undef; if ( ref( $_[0] ) eq 'HASH' ) { $post{'args'} = shift; } elsif ( scalar(@_) > 1 ) { tie my %hash, 'Tie::IxHash', @_; $post{'args'} = \%hash; } elsif ( scalar(@_) == 1 ) { $post{'content'} = shift; } else { die "https_post called with no params\n"; } $self->build_subs(qw( response_page response_code response_headers )); my( $res_page, $res_code, @res_headers)= Net::HTTPS::Any::https_post(%post); $self->response_page( $res_page ); $self->response_code( $res_code ); $self->response_headers( { @res_headers } ); ( $res_page, $res_code, @res_headers ); } =back =head1 SEE ALSO L, L =cut 1;