1 package Business::OnlinePayment::PlugnPay;
4 use vars qw($VERSION $DEBUG);
5 use Carp qw(carp croak);
7 use base qw(Business::OnlinePayment::HTTPS);
10 $VERSION = eval $VERSION;
17 my $level = shift || 0;
19 $self->{"__DEBUG"} = $level;
24 $Business::OnlinePayment::HTTPS::DEBUG = $level;
26 return ref($self) ? ( $self->{"__DEBUG"} || $DEBUG ) : $DEBUG;
33 # standard B::OP methods/data
34 $self->server("pay1.plugnpay.com");
36 $self->path("/payment/pnpremote.cgi");
39 order_number avs_code cvv2_response
40 response_page response_code response_headers
43 # module specific data
45 $self->debug( $opts{debug} );
50 foreach my $key (keys %opts) {
51 $key =~ /^default_(\w*)$/ or next;
52 $_defaults{$1} = $opts{$key};
55 $self->{_defaults} = \%_defaults;
62 my %content = $self->content();
66 'normal authorization' => 'auth', # Authorization/Settle transaction
67 'credit' => 'newreturn',# Credit (refund)
68 'void' => 'void', # Void
71 $content{'mode'} = $actions{ lc( $content{'action'} ) }
72 || $content{'action'};
78 'american express' => 'CC',
84 $content{'type'} = $types{ lc( $content{'type'} ) } || $content{'type'};
89 'ECHECK' => 'onlinecheck',
92 $content{'paymethod'} = $paymethods{ $content{'type'} };
94 $self->transaction_type( $content{'type'} );
96 $content{'transflags'} = 'recurring'
97 if lc( $content{'recurring_billing'} ) eq 'yes';
99 # stuff it back into %content
100 $self->content(%content);
104 my ( $self, %map ) = @_;
105 my %content = $self->content();
106 foreach ( keys %map ) {
110 : $content{ $map{$_} };
112 $self->content(%content);
117 my $expiration = shift;
119 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
120 my ( $month, $year ) = ( $1, $2 );
121 $expdate_mmyy = sprintf( "%02d/", $month ) . $year;
123 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
126 sub required_fields {
127 my($self,@fields) = @_;
130 my %content = $self->content();
133 if (exists $content{$_} && defined $content{$_} && $content{$_}=~/\S+/);
137 Carp::croak("missing required field(s): " . join(", ", @missing) . "\n")
145 die "Processor does not support a test mode"
146 if $self->test_transaction;
148 $self->_map_fields();
150 my %content = $self->content;
153 $required{CC_auth} = [ qw( mode publisher-name card-amount card-name
154 card-number card-exp paymethod ) ];
155 $required{CC_newreturn} = [ @{$required{CC_auth}}, qw( publisher-password ) ];
156 $required{CC_void} = [ qw( mode publisher-name publisher-password orderID
158 #$required{ECHECK_auth} = [ qw( mode publisher-name accttype routingnum
159 # accountnum checknum paymethod ) ];
161 $optional{CC_auth} = [ qw( publisher-email authtype required dontsndmail
162 easycard client convert cc-mail transflags
163 card-address1 card-address2 card-city card-state
164 card-prov card-zip card-country card-cvv
165 currency phone fax email shipinfo shipname
166 address1 address2 city state province zip
167 country ipaddress accttype orderID tax
168 shipping app-level order-id acct_code magstripe
169 marketdata carissuenum cardstartdate descrcodes
170 retailterms transflags ) ];
171 $optional{CC_newreturn} = [ qw( orderID card-address1 card-address2
172 card-city card-state card-zip card-country
175 $optional{CC_void} = [ qw( notify-email ) ];
177 #$optional{ECHECK_auth} = $optional{CC_auth}; # ?
178 #$optional{ECHECK_newreturn} = $optional{CC_newreturn}; # ? legal combo?
179 #$optional{ECHECK_void} = $optional{CC_void}; # ? legal combo?
181 my $type_action = $self->transaction_type(). '_'. $content{mode};
182 unless ( exists($required{$type_action}) ) {
183 $self->error_message("plugnpay can't handle transaction type: ".
184 "$content{action} on " . $self->transaction_type() );
185 $self->is_success(0);
189 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
191 $self->_revmap_fields(
193 'publisher-name' => 'login',
194 'publisher-password' => 'password',
196 'card-amount' => 'amount',
197 'card-name' => 'name',
198 'card-address1' => 'address',
199 'card-city' => 'city',
200 'card-state' => 'state',
202 'card-country' => 'country',
203 'card-number' => 'card_number',
204 'card-exp' => \$expdate_mmyy, # MMYY from 'expiration'
205 'card-cvv' => 'cvv2',
206 'order-id' => 'invoice_number',
207 'orderID' => 'order_number',
212 my %shipping_params = ( shipname => (($content{ship_first_name} || '') .
213 ' '. ($content{ship_last_name} || '')),
214 address1 => $content{ship_address},
215 map { $_ => $content{ "ship_$_" } }
216 qw ( city state zip country )
220 foreach ( keys ( %shipping_params ) ) {
221 if ($shipping_params{$_} && $shipping_params{$_} =~ /^\s*$/) {
222 delete $shipping_params{$_};
225 $shipping_params{shipinfo} = 1 if scalar(keys(%shipping_params));
227 my %params = ( $self->get_fields( @{$required{$type_action}},
228 @{$optional{$type_action}},
233 $params{'txn-type'} = 'auth' if $params{mode} eq 'void';
235 foreach ( keys ( %{($self->{_defaults})} ) ) {
236 $params{$_} = $self->{_defaults}->{$_} unless exists($params{$_});
240 $self->required_fields(@{$required{$type_action}});
242 warn join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $DEBUG > 1;
243 my ( $page, $resp, %resp_headers ) =
244 $self->https_post( %params );
246 $self->response_code( $resp );
247 $self->response_page( $page );
248 $self->response_headers( \%resp_headers );
250 warn "$page\n" if $DEBUG > 1;
251 # $page should contain key/value pairs
254 my %results = map { s/\s*$//;
255 my ($name, $value) = split '=', $_, 2;
256 $name =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
257 $value =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
261 # AVS and CVS values may be set on success or failure
262 $self->avs_code( $results{ 'avs-code' } );
263 $self->cvv2_response( $results{ cvvresp } );
264 $self->result_code( $results{ 'resp-code' } );
265 $self->order_number( $results{ orderID } );
266 $self->authorization( $results{ 'auth-code' } );
267 $self->error_message( $results{ MErrMsg } );
270 if ( $resp =~ /^(HTTP\S+ )?200/
271 &&($results{ FinalStatus } eq "success" ||
272 $results{ FinalStatus } eq "pending" && $results{ mode } eq 'newreturn'
275 $self->is_success(1);
277 $self->is_success(0);
287 Business::OnlinePayment::PlugnPay - plugnpay backend for Business::OnlinePayment
291 use Business::OnlinePayment;
293 my $tx = new Business::OnlinePayment( 'PlugnPay' );
295 # See the module documentation for details of content()
298 action => 'Normal Authorization',
299 description => 'Business::OnlinePayment::plugnpay test',
301 invoice_number => '100100',
302 customer_id => 'jef',
303 name => 'Jeff Finucane',
304 address => '123 Anystreet',
308 email => 'plugnpay@weasellips.com',
309 card_number => '4111111111111111',
310 expiration => '12/09',
312 order_number => 'string',
317 if ( $tx->is_success() ) {
319 "Card processed successfully: ", $tx->authorization, "\n",
320 "order number: ", $tx->order_number, "\n",
321 "CVV2 response: ", $tx->cvv2_response, "\n",
322 "AVS code: ", $tx->avs_code, "\n",
327 "Card was rejected: ", $tx->error_message, "\n",
328 "order number: ", $tx->order_number, "\n",
334 This module is a back end driver that implements the interface
335 specified by L<Business::OnlinePayment> to support payment handling
336 via plugnpay's payment solution.
338 See L<Business::OnlinePayment> for details on the interface this
341 =head1 Standard methods
347 This method sets the 'server' attribute to 'pay1.plugnpay.com' and
348 the port attribute to '443'. This method also sets up the
349 L</Module specific methods> described below.
355 =head1 Unofficial methods
357 This module provides the following methods which are not officially part of the
358 standard Business::OnlinePayment interface (as of 3.00_06) but are nevertheless
359 supported by multiple gateways modules and expected to be standardized soon:
363 =item L<order_number()|/order_number()>
365 =item L<avs_code()|/avs_code()>
367 =item L<cvv2_response()|/cvv2_response()>
371 =head1 Module specific methods
373 This module provides the following methods which are not currently
374 part of the standard Business::OnlinePayment interface:
378 =item L<expdate_mmyy()|/expdate_mmyy()>
380 =item L<debug()|/debug()>
386 The following default settings exist:
400 /payment/pnpremote.cgi
404 =head1 Parameters passed to constructor
406 If any of the key/value pairs passed to the constructor have a key
407 beginning with "default_" then those values are passed to plugnpay as
408 a the corresponding form field (without the "default_") whenever
409 content(%content) lacks that key.
411 =head1 Handling of content(%content)
413 The following rules apply to content(%content) data:
417 If 'type' matches one of the following keys it is replaced by the
418 right hand side value:
421 'mastercard' => 'CC',
422 'american express' => 'CC',
425 The value of 'type' is used to set transaction_type(). Currently this
426 module only supports the above values.
428 =head1 Setting plugnpay parameters from content(%content)
430 The following rules are applied to map data to plugnpay parameters
431 from content(%content):
433 # plugnpay param => $content{<key>}
434 publisher-name => 'login',
435 publisher-password => 'password',
437 card-amount => 'amount',
438 card-number => 'card_number',
439 card-exp => \( $month.$year ), # MM/YY from 'expiration'
441 order-id => 'invoice_number',
444 card-address1 => 'address',
446 card-state => 'state',
448 card-country => 'country',
449 orderID => 'order_number' # can be set via order_number()
451 shipname => 'ship_first_name' . ' ' . 'ship_last_name',
452 address1 => 'ship_address',
454 state => 'ship_state',
456 country => 'ship_country',
458 transflags => 'recurring' if ($content{recurring_billing}) eq 'yes',
460 =head1 Mapping plugnpay transaction responses to object methods
462 The following methods provides access to the transaction response data
463 resulting from a plugnpay request (after submit()) is called:
465 =head2 order_number()
467 This order_number() method returns the orderID field for transactions
468 to uniquely identify the transaction.
472 The result_code() method returns the resp-code field for transactions.
473 It is the alphanumeric return code indicating the outcome of the attempted
476 =head2 error_message()
478 The error_message() method returns the MErrMsg field for transactions.
479 This provides more details about the transaction result.
481 =head2 authorization()
483 The authorization() method returns the auth-code field,
484 which is the approval code obtained from the card processing network.
488 The avs_code() method returns the avs-code field from the transaction result.
490 =head2 cvv2_response()
492 The cvv2_response() method returns the cvvresp field, which is a
493 response message returned with the transaction result.
495 =head2 expdate_mmyy()
497 The expdate_mmyy() method takes a single scalar argument (typically
498 the value in $content{expiration}) and attempts to parse and format
499 and put the date in MM/YY format as required by the plugnpay
500 specification. If unable to parse the expiration date simply leave it
501 as is and let the plugnpay system attempt to handle it as-is.
505 Enable or disble debugging. The value specified here will also set
506 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
507 troubleshooting problems.
511 This module implements an interface to the plugnpay Remote Client Integration
512 Specification Rev. 10.03.2007
516 Jeff Finucane <plugnpay@weasellips.com>
518 Based on Business::OnlinePayment::PayflowPro written by Ivan Kohler
523 perl(1), L<Business::OnlinePayment>, L<Carp>, and the Remote Client Integration
524 Specification from plugnpay.