package Business::OnlinePayment::PaymentsGateway; use strict; use Carp; use Business::OnlinePayment; use Net::SSLeay qw(sslcat); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $DEBUG); require Exporter; @ISA = qw( Exporter AutoLoader Business::OnlinePayment); @EXPORT = qw(); @EXPORT_OK = qw(); $VERSION = '0.02'; $DEBUG = 0; my %pg_response_code = ( 'A01' => 'Transaction approved/completed', 'U01' => 'Merchant not allowed to access customer account', 'U02' => 'Customer account is in the ACH Direct "known bad" list', 'U03' => 'Merchant daily limit exceeded', 'U04' => 'Merchant monthly limit exceeded', 'U05' => 'AVS state/zipcode check failed', 'U06' => 'AVS state/area code check failed', 'U07' => 'AVS anonymous email check failed', 'U08' => 'Account has more transactions than the merchant\'s daily velocity'. ' limit allows for', 'U09' => 'Account has more transactions than the merchant\'s velocity'. ' window allows for', 'U10' => 'Transaction has the same attributes as another transaction'. ' within the time set by the merchant', 'U11' => '(RECUR TRANS NOT FOUND) Transaction types 40-42 only', 'U12' => 'Original transaction not voidable or capture-able', 'U13' => 'Transaction to be voided or captured was not found', 'U14' => 'void/capture and original transaction types do not agree (CC/EFT)', 'U18' => 'Void or Capture failed', #'U19' => 'Account ABA number if invalid', 'U19' => 'Account ABA number is invalid', 'U20' => 'Credit card number is invalid', 'U21' => 'Date is malformed', 'U22' => 'Swipe data is malformed', 'U23' => 'Malformed expiration date', 'U51' => 'Merchant is not "live"', 'U52' => 'Merchant not approved for transaction type (CC or EFT)', 'U53' => 'Transaction amount exceeds merchant\'s per transaction limit', 'U54' => 'Merchant\'s configuration requires updating - call customer'. ' support', 'U80' => 'Transaction was declined due to preauthorization (ATM Verify)'. ' result', 'U84' => 'Preauthorizer not responding', 'U85' => 'Preauthorizer error', 'U83' => 'Transaction was declined due to authorizer declination', 'U84' => 'Authorizer not responding', 'U85' => 'Authorizer error', 'U86' => 'Authorizer AVS check failed', 'F01' => 'Required field is missing', 'F03' => 'Name is not recognized', 'F04' => 'Value is not allowed', 'F05' => 'Field is repeated in message', 'F07' => 'Fields cannot both be present', #'E10' => 'Merchant id or password in incorrect', 'E10' => 'Merchant id or password is incorrect', 'E20' => 'Transaction message not received (I/O flush required?)', 'E90' => 'Originating IP not on merchant\'s approved IP list', 'E99' => 'An unspecified error has occurred', ); sub set_defaults { my $self = shift; $self->server('paymentsgateway.net'); $self->port( 5050 ); } sub map_fields { my $self = shift; my %content = $self->content(); #ACTION MAP my %actions = ( 'normal authorization' => 0, 'authorization only' => 1, 'post authorization' => 2, 'credit' => 3, ); my %types = ( 'visa' => 10, 'mastercard' => 10, 'american express' => 10, 'discover' => 10, 'cc' => 10, 'check' => 20, ); #pg_type/action = action + type $self->transaction_type( $actions{ lc($content{'action'}) } + $types{ lc($content{'type' }) } ); #$self->content(%content); } sub revmap_fields { my($self, %map) = @_; my %content = $self->content(); foreach(keys %map) { $content{$_} = ref($map{$_}) ? ${ $map{$_} } : $content{$map{$_}}; } $self->content(%content); } sub submit { my $self = shift; $self->map_fields(); #my %content = $self->content(); $self->revmap_fields( 'PG_MERCHANT_ID' => 'login', 'pg_password' => 'password', 'pg_transaction_type' => \($self->transaction_type()), #'pg_merchant_data_1' #... #'pg_merchant_data_9' 'pg_total_amount' => 'amount', #'pg_sales_tax_amount' 'pg_consumer_id' => 'customer_id', 'ecom_consumerorderid' => 'invoice_number', #??? #'ecom_walletid' => 'pg_billto_postal_name_company' => 'company', #???? 'ecom_billto_postal_name_first' => 'first_name', #???? 'ecom_billto_postal_name_last' => 'last_name', # ???? 'ecom_billto_postal_street_line1' => 'address', #'ecom_billto_postal_street_line2' 'ecom_billto_postal_city' => 'city', 'ecom_billto_postal_stateprov' => 'state', 'ecom_billto_postal_postalcode' => 'zip', 'ecom_billto_postal_countrycode' => 'country', 'ecom_billto_telecom_phone_number' => 'phone', 'ecom_billto_online_email' => 'email', #'pg_billto_ssn' #'pg_billto_dl_number' #'pg_billto_dl_state' 'ecom_payment_check_trn' => 'routing_code', 'ecom_payment_check_account' => 'account_number', 'ecom_payment_check_account_type' => \'C', #checking #'ecom_payment_check_checkno' => ); my %content = $self->content(); # name (first_name & last_name ) ? # fax # card_number exp_date #account_number routing_code bank_name my @fields = ( qw( PG_MERCHANT_ID pg_password pg_transaction_type ), ( map { "pg_merchant_$_" } ( 1 .. 9 ) ), qw( pg_total_amount pg_sales_tax_amount pg_consumer_id ecom_consumerorderid ecom_walletid pg_billto_postal_name_company ecom_billto_postal_name_first ecom_billto_postal_name_last ecom_billto_postal_street_line1 ecom_billto_postal_street_line2 ecom_billto_postal_city ecom_billto_postal_stateprov ecom_billto_postal_postalcode ecom_billto_postal_countrycode ecom_billto_telecom_phone_number ecom_billto_online_email pg_billto_ssn pg_billto_dl_number pg_billto_dl_state ) ); if ( $content{'type'} =~ /^check$/i ) { push @fields, qw( ecom_payment_check_trn ecom_payment_check_account ecom_payment_check_account_type ); } else { croak $content{'type'}. ' not (yet) supported'; } my $request = join("\n", map { "$_=". $content{$_} } grep { defined($content{$_}) && $content{$_} ne '' } @fields ). "\nendofdata\n"; warn $request if $DEBUG; warn "TEST: ". $self->test_transaction(). "\n" if $DEBUG; $self->port( $self->port() + 1000 ) if $self->test_transaction(); warn "SERVER ". $self->server(). "\n" if $DEBUG; warn "PORT ". $self->port(). "\n" if $DEBUG; my $reply = sslcat( $self->server(), $self->port(), $request ); die "no reply from server" unless $reply; warn "reply from server: $reply\n" if $DEBUG; my %response = map { /^(\w+)=(.*)$/ or /^(endofdata)()$/ or warn "can't parse response line: $_"; ($1, $2); } split(/\n/, $reply); if ( $response{'pg_response_type'} eq 'A' ) { $self->is_success(1); $self->result_code($response{'pg_response_code'}); $self->authorization($response{'pg_authorization_code'}); } else { $self->is_success(0); $self->result_code($response{'pg_response_code'}); $self->error_message( $pg_response_code{$response{'pg_response_code'}}. ': '. $response{'pg_response_description'} ); } } 1; __END__ =head1 NAME Business::OnlinePayment::PaymentsGateway - PaymentsGateway.Net backend for Business::OnlinePayment =head1 SYNOPSIS use Business::OnlinePayment; my $tx = new Business::OnlinePayment("PaymentsGateway"); $tx->content( type => 'CHECK', login => 'test', password => 'test', action => 'Normal Authorization', description => 'Business::OnlinePayment test', amount => '49.95', invoice_number => '100100', name => 'Tofu Beast', account_number => '12345', routing_code => '123456789', bank_name => 'First National Test Bank', ); $tx->submit(); if($tx->is_success()) { print "Card processed successfully: ".$tx->authorization."\n"; } else { print "Card was rejected: ".$tx->error_message."\n"; } =head1 DESCRIPTION For detailed information see L. =head1 NOTE This module only implements 'CHECK' (ACH) transactions at this time. Credit card transactions are not (yet) supported. =head1 COMPATIBILITY This module implements the interface documented in the "PaymentsGateway.net Integration Guide, Version 2.1, September 2002" =head1 AUTHOR Ivan Kohler =head1 SEE ALSO perl(1). L =cut