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 # stuff it back into %content
97 $self->content(%content);
101 my ( $self, %map ) = @_;
102 my %content = $self->content();
103 foreach ( keys %map ) {
107 : $content{ $map{$_} };
109 $self->content(%content);
114 my $expiration = shift;
116 if ( defined($expiration) and $expiration =~ /^(\d+)\D+\d*(\d{2})$/ ) {
117 my ( $month, $year ) = ( $1, $2 );
118 $expdate_mmyy = sprintf( "%02d/", $month ) . $year;
120 return defined($expdate_mmyy) ? $expdate_mmyy : $expiration;
123 sub required_fields {
124 my($self,@fields) = @_;
127 my %content = $self->content();
130 if (exists $content{$_} && defined $content{$_} && $content{$_}=~/\S+/);
134 Carp::croak("missing required field(s): " . join(", ", @missing) . "\n")
142 die "Processor does not support a test mode"
143 if $self->test_transaction;
145 $self->_map_fields();
147 my %content = $self->content;
150 $required{CC_auth} = [ qw( mode publisher-name card-amount card-name
151 card-number card-exp paymethod ) ];
152 $required{CC_newreturn} = [ @{$required{CC_auth}}, qw( publisher-password ) ];
153 $required{CC_void} = [ qw( mode publisher-name publisher-password orderID
155 #$required{ECHECK_auth} = [ qw( mode publisher-name accttype routingnum
156 # accountnum checknum paymethod ) ];
158 $optional{CC_auth} = [ qw( publisher-email authtype required dontsndmail
159 easycard client convert cc-mail transflags
160 card-address1 card-address2 card-city card-state
161 card-prov card-zip card-country card-cvv
162 currency phone fax email shipinfo shipname
163 address1 address2 city state province zip
164 country ipaddress accttype orderID tax
165 shipping app-level order-id acct_code magstripe
166 marketdata carissuenum cardstartdate descrcodes
168 $optional{CC_newreturn} = [ qw( orderID card-address1 card-address2
169 card-city card-state card-zip card-country
172 $optional{CC_void} = [ qw( notify-email ) ];
174 #$optional{ECHECK_auth} = $optional{CC_auth}; # ?
175 #$optional{ECHECK_newreturn} = $optional{CC_newreturn}; # ? legal combo?
176 #$optional{ECHECK_void} = $optional{CC_void}; # ? legal combo?
178 my $type_action = $self->transaction_type(). '_'. $content{mode};
179 unless ( exists($required{$type_action}) ) {
180 $self->error_message("plugnpay can't handle transaction type: ".
181 "$content{action} on " . $self->transaction_type() );
182 $self->is_success(0);
186 my $expdate_mmyy = $self->expdate_mmyy( $content{"expiration"} );
188 $self->_revmap_fields(
190 'publisher-name' => 'login',
191 'publisher-password' => 'password',
193 'card-amount' => 'amount',
194 'card-name' => 'name',
195 'card-address1' => 'address',
196 'card-city' => 'city',
197 'card-state' => 'state',
199 'card-country' => 'country',
200 'card-number' => 'card_number',
201 'card-exp' => \$expdate_mmyy, # MMYY from 'expiration'
202 'card-cvv' => 'cvv2',
203 'order-id' => 'invoice_number',
204 'orderID' => 'order_number',
209 my %shipping_params = ( shipname => (($content{ship_first_name} || '') .
210 ' '. ($content{ship_last_name} || '')),
211 address1 => $content{ship_address},
212 map { $_ => $content{ "ship_$_" } }
213 qw ( city state zip country )
217 foreach ( keys ( %shipping_params ) ) {
218 if ($shipping_params{$_} && $shipping_params{$_} =~ /^\s*$/) {
219 delete $shipping_params{$_};
222 $shipping_params{shipinfo} = 1 if scalar(keys(%shipping_params));
224 my %params = ( $self->get_fields( @{$required{$type_action}},
225 @{$optional{$type_action}},
230 $params{'txn-type'} = 'auth' if $params{mode} eq 'void';
232 foreach ( keys ( %{($self->{_defaults})} ) ) {
233 $params{$_} = $self->{_defaults}->{$_} unless exists($params{$_});
237 $self->required_fields(@{$required{$type_action}});
239 warn join("\n", map{ "$_ => $params{$_}" } keys(%params)) if $DEBUG > 1;
240 my ( $page, $resp, %resp_headers ) =
241 $self->https_post( %params );
243 $self->response_code( $resp );
244 $self->response_page( $page );
245 $self->response_headers( \%resp_headers );
247 warn "$page\n" if $DEBUG > 1;
248 # $page should contain key/value pairs
251 my %results = map { s/\s*$//;
252 my ($name, $value) = split '=', $_, 2;
253 $name =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
254 $value =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
258 # AVS and CVS values may be set on success or failure
259 $self->avs_code( $results{ 'avs-code' } );
260 $self->cvv2_response( $results{ cvvresp } );
261 $self->result_code( $results{ 'resp-code' } );
262 $self->order_number( $results{ orderID } );
263 $self->authorization( $results{ 'auth-code' } );
264 $self->error_message( $results{ MErrMsg } );
267 if ( $resp =~ /^(HTTP\S+ )?200/
268 &&($results{ FinalStatus } eq "success" ||
269 $results{ FinalStatus } eq "pending" && $results{ mode } eq 'newreturn'
272 $self->is_success(1);
274 $self->is_success(0);
284 Business::OnlinePayment::PlugnPay - plugnpay backend for Business::OnlinePayment
288 use Business::OnlinePayment;
290 my $tx = new Business::OnlinePayment( 'PlugnPay' );
292 # See the module documentation for details of content()
295 action => 'Normal Authorization',
296 description => 'Business::OnlinePayment::plugnpay test',
298 invoice_number => '100100',
299 customer_id => 'jef',
300 name => 'Jeff Finucane',
301 address => '123 Anystreet',
305 email => 'plugnpay@weasellips.com',
306 card_number => '4111111111111111',
307 expiration => '12/09',
309 order_number => 'string',
314 if ( $tx->is_success() ) {
316 "Card processed successfully: ", $tx->authorization, "\n",
317 "order number: ", $tx->order_number, "\n",
318 "CVV2 response: ", $tx->cvv2_response, "\n",
319 "AVS code: ", $tx->avs_code, "\n",
324 "Card was rejected: ", $tx->error_message, "\n",
325 "order number: ", $tx->order_number, "\n",
331 This module is a back end driver that implements the interface
332 specified by L<Business::OnlinePayment> to support payment handling
333 via plugnpay's payment solution.
335 See L<Business::OnlinePayment> for details on the interface this
338 =head1 Standard methods
344 This method sets the 'server' attribute to 'pay1.plugnpay.com' and
345 the port attribute to '443'. This method also sets up the
346 L</Module specific methods> described below.
352 =head1 Unofficial methods
354 This module provides the following methods which are not officially part of the
355 standard Business::OnlinePayment interface (as of 3.00_06) but are nevertheless
356 supported by multiple gateways modules and expected to be standardized soon:
360 =item L<order_number()|/order_number()>
362 =item L<avs_code()|/avs_code()>
364 =item L<cvv2_response()|/cvv2_response()>
368 =head1 Module specific methods
370 This module provides the following methods which are not currently
371 part of the standard Business::OnlinePayment interface:
375 =item L<expdate_mmyy()|/expdate_mmyy()>
377 =item L<debug()|/debug()>
383 The following default settings exist:
397 /payment/pnpremote.cgi
401 =head1 Parameters passed to constructor
403 If any of the key/value pairs passed to the constructor have a key
404 beginning with "default_" then those values are passed to plugnpay as
405 a the corresponding form field (without the "default_") whenever
406 content(%content) lacks that key.
408 =head1 Handling of content(%content)
410 The following rules apply to content(%content) data:
414 If 'type' matches one of the following keys it is replaced by the
415 right hand side value:
418 'mastercard' => 'CC',
419 'american express' => 'CC',
422 The value of 'type' is used to set transaction_type(). Currently this
423 module only supports the above values.
425 =head1 Setting plugnpay parameters from content(%content)
427 The following rules are applied to map data to plugnpay parameters
428 from content(%content):
430 # plugnpay param => $content{<key>}
431 publisher-name => 'login',
432 publisher-password => 'password',
434 card-amount => 'amount',
435 card-number => 'card_number',
436 card-exp => \( $month.$year ), # MM/YY from 'expiration'
438 order-id => 'invoice_number',
441 card-address1 => 'address',
443 card-state => 'state',
445 card-country => 'country',
446 orderID => 'order_number' # can be set via order_number()
448 shipname => 'ship_first_name' . ' ' . 'ship_last_name',
449 address1 => 'ship_address',
451 state => 'ship_state',
453 country => 'ship_country',
456 =head1 Mapping plugnpay transaction responses to object methods
458 The following methods provides access to the transaction response data
459 resulting from a plugnpay request (after submit()) is called:
461 =head2 order_number()
463 This order_number() method returns the orderID field for transactions
464 to uniquely identify the transaction.
468 The result_code() method returns the resp-code field for transactions.
469 It is the alphanumeric return code indicating the outcome of the attempted
472 =head2 error_message()
474 The error_message() method returns the MErrMsg field for transactions.
475 This provides more details about the transaction result.
477 =head2 authorization()
479 The authorization() method returns the auth-code field,
480 which is the approval code obtained from the card processing network.
484 The avs_code() method returns the avs-code field from the transaction result.
486 =head2 cvv2_response()
488 The cvv2_response() method returns the cvvresp field, which is a
489 response message returned with the transaction result.
491 =head2 expdate_mmyy()
493 The expdate_mmyy() method takes a single scalar argument (typically
494 the value in $content{expiration}) and attempts to parse and format
495 and put the date in MM/YY format as required by the plugnpay
496 specification. If unable to parse the expiration date simply leave it
497 as is and let the plugnpay system attempt to handle it as-is.
501 Enable or disble debugging. The value specified here will also set
502 $Business::OnlinePayment::HTTPS::DEBUG in submit() to aid in
503 troubleshooting problems.
507 This module implements an interface to the plugnpay Remote Client Integration
508 Specification Rev. 10.03.2007
512 Jeff Finucane <plugnpay@weasellips.com>
514 Based on Business::OnlinePayment::PayflowPro written by Ivan Kohler
519 perl(1), L<Business::OnlinePayment>, L<Carp>, and the Remote Client Integration
520 Specification from plugnpay.