1 package Business::OnlinePayment::IPPay;
9 use Business::OnlinePayment;
10 use Business::OnlinePayment::HTTPS;
11 use vars qw($VERSION $DEBUG @ISA $me);
13 @ISA = qw(Business::OnlinePayment::HTTPS);
15 $VERSION = eval $VERSION; # modperlstyle: convert the string into a number
18 $me = 'Business::OnlinePayment::IPPay';
22 'info_version' => '0.01',
23 'module_version' => $VERSION,
24 'supported_types' => [ qw( CC ECHECK ) ],
25 'supported_actions' => { 'CC' => [
26 'Normal Authorization',
33 'Normal Authorization',
38 'CC_void_requires_card' => 1,
46 # standard B::OP methods/data
47 $self->server('gateway17.jetpay.com') unless $self->server;
48 $self->port('443') unless $self->port;
49 $self->path('/jetpay') unless $self->path;
51 $self->build_subs(qw( order_number avs_code cvv2_response
52 response_page response_code response_headers
55 # module specific data
57 $self->debug( $opts{debug} );
62 foreach my $key (keys %opts) {
63 $key =~ /^default_(\w*)$/ or next;
64 $_defaults{$1} = $opts{$key};
67 $self->{_defaults} = \%_defaults;
73 my %content = $self->content();
76 my %types = ( 'visa' => 'CC',
78 'american express' => 'CC',
82 $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
83 $self->transaction_type($content{'type'});
86 my $action = lc($content{'action'});
88 ( 'normal authorization' => 'SALE',
89 'authorization only' => 'AUTHONLY',
90 'post authorization' => 'CAPT',
95 ( 'normal authorization' => 'CHECK',
97 'credit' => 'REVERSAL',
99 if ($self->transaction_type eq 'CC') {
100 $content{'TransactionType'} = $actions{$action} || $action;
101 }elsif ($self->transaction_type eq 'ECHECK') {
102 $content{'TransactionType'} = $check_actions{$action} || $action;
107 my %account_types = ('personal checking' => 'Checking',
108 'personal savings' => 'Savings',
109 'business checking' => 'BusinessCk',
111 $content{'account_type'} = $account_types{lc($content{'account_type'})}
112 || $content{'account_type'};
114 $content{Origin} = 'RECURRING'
115 if ($content{recurring_billing} &&$content{recurring_billing} eq 'YES' );
117 # stuff it back into %content
118 $self->content(%content);
123 my ($self, $exp) = (shift, shift);
125 if ( defined($exp) and $exp =~ /^(\d+)\D+\d*\d{2}$/ ) {
126 $month = sprintf( "%02d", $1 );
127 }elsif ( defined($exp) and $exp =~ /^(\d{2})\d{2}$/ ) {
128 $month = sprintf( "%02d", $1 );
134 my ($self, $exp) = (shift, shift);
136 if ( defined($exp) and $exp =~ /^\d+\D+\d*(\d{2})$/ ) {
137 $year = sprintf( "%02d", $1 );
138 }elsif ( defined($exp) and $exp =~ /^\d{2}(\d{2})$/ ) {
139 $year = sprintf( "%02d", $1 );
146 tie my(%map), 'Tie::IxHash', @_;
147 my %content = $self->content();
150 if ( ref( $map{$_} ) eq 'HASH' ) {
151 $value = $map{$_} if ( keys %{ $map{$_} } );
152 }elsif( ref( $map{$_} ) ) {
153 $value = ${ $map{$_} };
154 }elsif( exists( $content{ $map{$_} } ) ) {
155 $value = $content{ $map{$_} };
158 if (defined($value)) {
169 $self->is_success(0);
172 my @required_fields = qw(action login type);
174 my $action = lc($self->{_content}->{action});
175 my $type = $self->transaction_type();
176 if ( $action eq 'normal authorization'
177 || $action eq 'credit'
178 || $action eq 'authorization only' && $type eq 'CC')
180 push @required_fields, qw( amount );
182 push @required_fields, qw( card_number expiration )
185 push @required_fields,
186 qw( routing_code account_number name ) # account_type
187 if ($type eq "ECHECK");
189 }elsif ( $action eq 'post authorization' && $type eq 'CC') {
190 push @required_fields, qw( order_number );
191 }elsif ( $action eq 'void') {
192 push @required_fields, qw( order_number amount );
194 push @required_fields, qw( authorization card_number )
197 push @required_fields,
198 qw( routing_code account_number name ) # account_type
199 if ($type eq "ECHECK");
202 croak "$me can't handle transaction type: ".
203 $self->{_content}->{action}. " for ".
204 $self->transaction_type();
207 my %content = $self->content();
208 foreach ( keys ( %{($self->{_defaults})} ) ) {
209 $content{$_} = $self->{_defaults}->{$_} unless exists($content{$_});
211 $self->content(%content);
213 $self->required_fields(@required_fields);
215 if ($self->test_transaction()) {
216 $self->server('test1.jetpay.com');
218 $self->path('/jetpay');
221 my $transaction_id = $content{'order_number'};
222 unless ($transaction_id) {
223 my ($page, $server_response, %headers) = $self->https_get('dummy' => 1);
224 warn "fetched transaction id: (HTTPS response: $server_response) ".
226 join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
227 "(Raw HTTPS content: $page)"
229 return unless $server_response=~ /^200/;
230 $transaction_id = $page;
233 my $cardexpmonth = $self->expdate_month($content{expiration});
234 my $cardexpyear = $self->expdate_year($content{expiration});
235 my $cardstartmonth = $self->expdate_month($content{card_start});
236 my $cardstartyear = $self->expdate_year($content{card_start});
239 if (defined($content{amount})) {
240 $amount = sprintf("%.2f", $content{amount});
244 my $check_number = $content{check_number} || "100" # make one up
245 if($content{account_number});
247 my $terminalid = $content{login} if $type eq 'CC';
248 my $merchantid = $content{login} if $type eq 'ECHECK';
250 my $country = country2code( $content{country}, LOCALE_CODE_ALPHA_3 );
251 $country = country_code2code( $content{country},
256 $country = $content{country}
258 $country = uc($country) if $country;
261 country2code( $content{ship_country}, LOCALE_CODE_ALPHA_3 );
262 $ship_country = country_code2code( $content{ship_country},
266 unless $ship_country;
267 $ship_country = $content{ship_country}
268 unless $ship_country;
269 $ship_country = uc($ship_country) if $ship_country;
271 tie my %ach, 'Tie::IxHash',
272 $self->revmap_fields(
273 #AccountType => 'account_type',
274 AccountNumber => 'account_number',
275 ABA => 'routing_code',
276 CheckNumber => \$check_number,
279 tie my %industryinfo, 'Tie::IxHash',
280 $self->revmap_fields(
281 Type => 'IndustryInfo',
284 tie my %shippingaddr, 'Tie::IxHash',
285 $self->revmap_fields(
286 Address => 'ship_address',
288 StateProv => 'ship_state',
289 Country => \$ship_country,
290 Phone => 'ship_phone',
293 unless ( $type ne 'CC' || keys %shippingaddr ) {
294 tie %shippingaddr, 'Tie::IxHash',
295 $self->revmap_fields(
296 Address => 'address',
298 StateProv => 'state',
299 Country => \$country,
304 tie my %shippinginfo, 'Tie::IxHash',
305 $self->revmap_fields(
306 CustomerPO => 'CustomerPO',
307 ShippingMethod => 'ShippingMethod',
308 ShippingName => 'ship_name',
309 ShippingAddr => \%shippingaddr,
312 tie my %req, 'Tie::IxHash',
313 $self->revmap_fields(
314 TransactionType => 'TransactionType',
315 TerminalID => 'login',
316 # TerminalID => \$terminalid,
317 # MerchantID => \$merchantid,
318 TransactionID => \$transaction_id,
319 RoutingCode => 'RoutingCode',
320 Approval => 'authorization',
321 BatchID => 'BatchID',
323 Password => 'password',
324 OrderNumber => 'invoice_number',
325 CardNum => 'card_number',
327 Issue => 'issue_number',
328 CardExpMonth => \$cardexpmonth,
329 CardExpYear => \$cardexpyear,
330 CardStartMonth => \$cardstartmonth,
331 CardStartYear => \$cardstartyear,
336 DispositionType => 'DispositionType',
337 TotalAmount => \$amount,
338 FeeAmount => 'FeeAmount',
339 TaxAmount => 'TaxAmount',
340 BillingAddress => 'address',
341 BillingCity => 'city',
342 BillingStateProv => 'state',
343 BillingPostalCode => 'zip',
344 BillingCountry => \$country,
345 BillingPhone => 'phone',
347 UserIPAddr => 'customer_ip',
348 UserHost => 'UserHost',
349 UDField1 => 'UDField1',
350 UDField2 => 'UDField2',
351 UDField3 => 'UDField3',
352 ActionCode => 'ActionCode',
353 IndustryInfo => \%industryinfo,
354 ShippingInfo => \%shippinginfo,
358 my $writer = new XML::Writer( OUTPUT => \$post_data,
361 ENCODING => 'us-ascii',
364 $writer->startTag('JetPay');
365 foreach ( keys ( %req ) ) {
366 $self->_xmlwrite($writer, $_, $req{$_});
368 $writer->endTag('JetPay');
371 warn "$post_data\n" if $DEBUG;
373 my ($page,$server_response,%headers) = $self->https_post($post_data);
375 warn "$page\n" if $DEBUG;
378 if ($server_response =~ /^200/){
379 $response = XMLin($page);
380 if ( exists($response->{ActionCode}) && !exists($response->{ErrMsg})) {
381 $self->error_message($response->{ResponseText});
383 $self->error_message($response->{Errmsg});
386 # $self->error_message("Server Failed");
389 $self->result_code($response->{ActionCode} || '');
390 $self->order_number($response->{TransactionID} || '');
391 $self->authorization($response->{Approval} || '');
392 $self->cvv2_response($response->{CVV2} || '');
393 $self->avs_code($response->{AVS} || '');
395 $self->is_success($self->result_code() eq '000' ? 1 : 0);
397 unless ($self->is_success()) {
398 unless ( $self->error_message() ) { #additional logging information
399 $self->error_message(
400 "(HTTPS response: $server_response) ".
402 join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
403 "(Raw HTTPS content: $page)"
411 my ($self, $writer, $item, $value) = @_;
412 $writer->startTag($item);
413 if ( ref( $value ) eq 'HASH' ) {
414 foreach ( keys ( %$value ) ) {
415 $self->_xmlwrite($writer, $_, $value->{$_});
418 $writer->characters($value);
420 $writer->endTag($item);
429 Business::OnlinePayment::IPPay - IPPay backend for Business::OnlinePayment
433 use Business::OnlinePayment;
436 new Business::OnlinePayment( "IPPay",
437 'default_Origin' => 'PHONE ORDER',
441 login => 'testdrive',
442 password => '', #password
443 action => 'Normal Authorization',
444 description => 'Business::OnlinePayment test',
446 customer_id => 'tfb',
447 name => 'Tofu Beast',
448 address => '123 Anystreet',
452 card_number => '4007000000027',
453 expiration => '09/02',
454 cvv2 => '1234', #optional
458 if($tx->is_success()) {
459 print "Card processed successfully: ".$tx->authorization."\n";
461 print "Card was rejected: ".$tx->error_message."\n";
464 =head1 SUPPORTED TRANSACTION TYPES
466 =head2 CC, Visa, MasterCard, American Express, Discover
468 Content required: type, login, action, amount, card_number, expiration.
472 Content required: type, login, action, amount, name, account_number, routing_code.
476 For detailed information see L<Business::OnlinePayment>.
478 =head1 METHODS AND FUNCTIONS
480 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.
484 Returns the response error code.
488 Returns the response error description text.
490 =head2 server_response
492 Returns the complete response from the server.
494 =head1 Handling of content(%content) data:
498 The following actions are valid
506 =head1 Setting IPPay parameters from content(%content)
508 The following rules are applied to map data to IPPay parameters
509 from content(%content):
511 # param => $content{<key>}
512 TransactionType => 'TransactionType',
513 TerminalID => 'login',
514 TransactionID => 'order_number',
515 RoutingCode => 'RoutingCode',
516 Approval => 'authorization',
517 BatchID => 'BatchID',
519 Password => 'password',
520 OrderNumber => 'invoice_number',
521 CardNum => 'card_number',
523 Issue => 'issue_number',
524 CardExpMonth => \( $month ), # MM from MM(-)YY(YY) of 'expiration'
525 CardExpYear => \( $year ), # YY from MM(-)YY(YY) of 'expiration'
526 CardStartMonth => \( $month ), # MM from MM(-)YY(YY) of 'card_start'
527 CardStartYear => \( $year ), # YY from MM(-)YY(YY) of 'card_start'
531 AccountNumber => 'account_number',
532 ABA => 'routing_code',
533 CheckNumber => 'check_number',
535 DispositionType => 'DispositionType',
536 TotalAmount => 'amount' reformatted into cents
537 FeeAmount => 'FeeAmount',
538 TaxAmount => 'TaxAmount',
539 BillingAddress => 'address',
540 BillingCity => 'city',
541 BillingStateProv => 'state',
542 BillingPostalCode => 'zip',
543 BillingCountry => 'country', # forced to ISO-3166-alpha-3
544 BillingPhone => 'phone',
546 UserIPAddr => 'customer_ip',
547 UserHost => 'UserHost',
548 UDField1 => 'UDField1',
549 UDField2 => 'UDField2',
550 UDField3 => 'UDField3',
551 ActionCode => 'ActionCode',
553 Type => 'IndustryInfo',
555 CustomerPO => 'CustomerPO',
556 ShippingMethod => 'ShippingMethod',
557 ShippingName => 'ship_name',
559 Address => 'ship_address',
561 StateProv => 'ship_state',
562 Country => 'ship_country', # forced to ISO-3166-alpha-3
563 Phone => 'ship_phone',
569 Business::OnlinePayment::IPPay uses IPPay XML Product Specifications version
572 See http://www.ippay.com/ for more information.
576 Jeff Finucane, ippay@weasellips.com
580 perl(1). L<Business::OnlinePayment>.