1 package Business::OnlinePayment::IPPay;
10 use HTTP::Request::Common qw (POST);
11 use Date::Calc qw(Add_Delta_YM Add_Delta_Days);
12 use Business::OnlinePayment;
13 use Business::OnlinePayment::HTTPS;
14 use vars qw($VERSION $DEBUG @ISA $me);
16 @ISA = qw(Business::OnlinePayment::HTTPS);
19 $me = 'Business::OnlinePayment::IPPay';
25 # standard B::OP methods/data
26 $self->server('gateway17.jetpay.com') unless $self->server;
27 $self->port('443') unless $self->port;
28 $self->path('/jetpay') unless $self->path;
30 $self->build_subs(qw( order_number avs_code cvv2_response
31 response_page response_code response_headers
34 # module specific data
36 $self->debug( $opts{debug} );
41 foreach my $key (keys %opts) {
42 $key =~ /^default_(\w*)$/ or next;
43 $_defaults{$1} = $opts{$key};
46 $self->{_defaults} = \%_defaults;
52 my %content = $self->content();
55 my %types = ( 'visa' => 'CC',
57 'american express' => 'CC',
61 $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
62 $self->transaction_type($content{'type'});
65 my $action = lc($content{'action'});
67 ( 'normal authorization' => 'SALE',
68 'authorization only' => 'AUTHONLY',
69 'post authorization' => 'CAPT',
74 ( 'normal authorization' => 'CHECK',
76 'credit' => 'REVERSAL',
78 if ($self->transaction_type eq 'CC') {
79 $content{'TransactionType'} = $actions{$action} || $action;
80 }elsif ($self->transaction_type eq 'ECHECK') {
81 $content{'TransactionType'} = $check_actions{$action} || $action;
86 my %account_types = ('personal checking' => 'Checking',
87 'personal savings' => 'Savings',
88 'business checking' => 'BusinessCk',
90 $content{'account_type'} = $account_types{lc($content{'account_type'})}
91 || $content{'account_type'};
93 $content{Origin} = 'RECURRING'
94 if ($content{recurring_billing} &&$content{recurring_billing} eq 'YES' );
96 # stuff it back into %content
97 $self->content(%content);
102 my ($self, $exp) = (shift, shift);
104 if ( defined($exp) and $exp =~ /^(\d+)\D+\d*\d{2}$/ ) {
105 $month = sprintf( "%02d", $1 );
106 }elsif ( defined($exp) and $exp =~ /^(\d{2})\d{2}$/ ) {
107 $month = sprintf( "%02d", $1 );
113 my ($self, $exp) = (shift, shift);
115 if ( defined($exp) and $exp =~ /^\d+\D+\d*(\d{2})$/ ) {
116 $year = sprintf( "%02d", $1 );
117 }elsif ( defined($exp) and $exp =~ /^\d{2}(\d{2})$/ ) {
118 $year = sprintf( "%02d", $1 );
125 tie my(%map), 'Tie::IxHash', @_;
126 my %content = $self->content();
129 if ( ref( $map{$_} ) eq 'HASH' ) {
130 $value = $map{$_} if ( keys %{ $map{$_} } );
131 }elsif( ref( $map{$_} ) ) {
132 $value = ${ $map{$_} };
133 }elsif( exists( $content{ $map{$_} } ) ) {
134 $value = $content{ $map{$_} };
137 if (defined($value)) {
148 $self->is_success(0);
151 my @required_fields = qw(action login type);
153 my $action = lc($self->{_content}->{action});
154 my $type = $self->transaction_type();
155 if ( $action eq 'normal authorization'
156 || $action eq 'credit'
157 || $action eq 'authorization only' && $type eq 'CC')
159 push @required_fields, qw( amount );
161 push @required_fields, qw( card_number expiration )
164 push @required_fields,
165 qw( routing_code account_number name ) # account_type
166 if ($type eq "ECHECK");
168 }elsif ( $action eq 'post authorization' && $type eq 'CC') {
169 push @required_fields, qw( order_number );
170 }elsif ( $action eq 'void') {
171 push @required_fields, qw( order_number amount );
173 push @required_fields, qw( authorization card_number )
176 push @required_fields,
177 qw( routing_code account_number name ) # account_type
178 if ($type eq "ECHECK");
181 croak "$me can't handle transaction type: ".
182 $self->{_content}->{action}. " for ".
183 $self->transaction_type();
186 my %content = $self->content();
187 foreach ( keys ( %{($self->{_defaults})} ) ) {
188 $content{$_} = $self->{_defaults}->{$_} unless exists($content{$_});
190 $self->content(%content);
192 $self->required_fields(@required_fields);
194 my $transaction_id = $content{'order_number'};
195 unless ($transaction_id) {
196 my ($page, $server_response, %headers) = $self->https_get('dummy' => 1);
197 return unless $server_response=~ /^200/;
198 $transaction_id = $page;
201 my $cardexpmonth = $self->expdate_month($content{expiration});
202 my $cardexpyear = $self->expdate_year($content{expiration});
203 my $cardstartmonth = $self->expdate_month($content{card_start});
204 my $cardstartyear = $self->expdate_year($content{card_start});
207 if (defined($content{amount})) {
208 $amount = sprintf("%.2f", $content{amount});
212 my $check_number = $content{check_number} || "100" # make one up
213 if($content{account_number});
215 my $terminalid = $content{login} if $type eq 'CC';
216 my $merchantid = $content{login} if $type eq 'ECHECK';
218 tie my %ach, 'Tie::IxHash',
219 $self->revmap_fields(
220 #AccountType => 'account_type',
221 AccountNumber => 'account_number',
222 ABA => 'routing_code',
223 CheckNumber => \$check_number,
226 tie my %industryinfo, 'Tie::IxHash',
227 $self->revmap_fields(
228 Type => 'IndustryInfo',
231 tie my %shippingaddr, 'Tie::IxHash',
232 $self->revmap_fields(
233 Address => 'ship_address',
235 StateProv => 'ship_state',
236 Country => 'ship_country',
237 Phone => 'ship_phone',
240 unless ( $type ne 'CC' || keys %shippingaddr ) {
241 tie %shippingaddr, 'Tie::IxHash',
242 $self->revmap_fields(
243 Address => 'address',
245 StateProv => 'state',
246 Country => 'country',
251 tie my %shippinginfo, 'Tie::IxHash',
252 $self->revmap_fields(
253 CustomerPO => 'CustomerPO',
254 ShippingMethod => 'ShippingMethod',
255 ShippingName => 'ship_name',
256 ShippingAddr => \%shippingaddr,
259 tie my %req, 'Tie::IxHash',
260 $self->revmap_fields(
261 TransactionType => 'TransactionType',
262 TerminalID => 'login',
263 # TerminalID => \$terminalid,
264 # MerchantID => \$merchantid,
265 TransactionID => \$transaction_id,
266 RoutingCode => 'RoutingCode',
267 Approval => 'authorization',
268 BatchID => 'BatchID',
270 Password => 'password',
271 OrderNumber => 'invoice_number',
272 CardNum => 'card_number',
274 Issue => 'issue_number',
275 CardExpMonth => \$cardexpmonth,
276 CardExpYear => \$cardexpyear,
277 CardStartMonth => \$cardstartmonth,
278 CardStartYear => \$cardstartyear,
283 DispositionType => 'DispositionType',
284 TotalAmount => \$amount,
285 FeeAmount => 'FeeAmount',
286 TaxAmount => 'TaxAmount',
287 BillingAddress => 'address',
288 BillingCity => 'city',
289 BillingStateProv => 'state',
290 BillingPostalCode => 'zip',
291 BillingCountry => 'country',
292 BillingPhone => 'phone',
294 UserIPAddr => 'customer_ip',
295 UserHost => 'UserHost',
296 UDField1 => 'UDField1',
297 UDField2 => 'UDField2',
298 UDField3 => 'UDField3',
299 ActionCode => 'ActionCode',
300 IndustryInfo => \%industryinfo,
301 ShippingInfo => \%shippinginfo,
305 my $writer = new XML::Writer( OUTPUT => \$post_data,
308 ENCODING => 'us-ascii',
311 $writer->startTag('JetPay');
312 foreach ( keys ( %req ) ) {
313 $self->_xmlwrite($writer, $_, $req{$_});
315 $writer->endTag('JetPay');
318 if ($self->test_transaction()) {
319 $self->server('test1.jetpay.com');
321 $self->path('/jetpay');
324 warn "$post_data\n" if $DEBUG;
326 my ($page,$server_response,%headers) = $self->https_post($post_data);
328 warn "$page\n" if $DEBUG;
331 if ($server_response =~ /^200/){
332 $response = XMLin($page);
333 if ( exists($response->{ActionCode}) && !exists($response->{ErrMsg})) {
334 $self->error_message($response->{ResponseText});
336 $self->error_message($response->{Errmsg});
339 # $self->error_message("Server Failed");
342 $self->result_code($response->{ActionCode} || '');
343 $self->order_number($response->{TransactionID} || '');
344 $self->authorization($response->{Approval} || '');
345 $self->cvv2_response($response->{CVV2} || '');
346 $self->avs_code($response->{AVS} || '');
348 $self->is_success($self->result_code() eq '000' ? 1 : 0);
350 unless ($self->is_success()) {
351 unless ( $self->error_message() ) { #additional logging information
352 $self->error_message(
353 "(HTTPS response: $server_response) ".
355 join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
356 "(Raw HTTPS content: $page)"
364 my ($self, $writer, $item, $value) = @_;
365 $writer->startTag($item);
366 if ( ref( $value ) eq 'HASH' ) {
367 foreach ( keys ( %$value ) ) {
368 $self->_xmlwrite($writer, $_, $value->{$_});
371 $writer->characters($value);
373 $writer->endTag($item);
381 Business::OnlinePayment::IPPay - IPPay backend for Business::OnlinePayment
385 use Business::OnlinePayment;
388 new Business::OnlinePayment( "IPPay",
389 'default_Origin' => 'PHONE ORDER',
393 login => 'testdrive',
394 password => '', #password
395 action => 'Normal Authorization',
396 description => 'Business::OnlinePayment test',
398 customer_id => 'tfb',
399 name => 'Tofu Beast',
400 address => '123 Anystreet',
404 card_number => '4007000000027',
405 expiration => '09/02',
406 cvv2 => '1234', #optional
410 if($tx->is_success()) {
411 print "Card processed successfully: ".$tx->authorization."\n";
413 print "Card was rejected: ".$tx->error_message."\n";
416 =head1 SUPPORTED TRANSACTION TYPES
418 =head2 CC, Visa, MasterCard, American Express, Discover
420 Content required: type, login, action, amount, card_number, expiration.
424 Content required: type, login, action, amount, name, account_number, routing_code.
428 For detailed information see L<Business::OnlinePayment>.
430 =head1 METHODS AND FUNCTIONS
432 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.
436 Returns the response error code.
440 Returns the response error description text.
442 =head2 server_response
444 Returns the complete response from the server.
446 =head1 Handling of content(%content) data:
450 The following actions are valid
458 =head1 Setting IPPay parameters from content(%content)
460 The following rules are applied to map data to IPPay parameters
461 from content(%content):
463 # param => $content{<key>}
464 TransactionType => 'TransactionType',
465 TerminalID => 'login',
466 TransactionID => 'order_number',
467 RoutingCode => 'RoutingCode',
468 Approval => 'authorization',
469 BatchID => 'BatchID',
471 Password => 'password',
472 OrderNumber => 'invoice_number',
473 CardNum => 'card_number',
475 Issue => 'issue_number',
476 CardExpMonth => \( $month ), # MM from MM(-)YY(YY) of 'expiration'
477 CardExpYear => \( $year ), # YY from MM(-)YY(YY) of 'expiration'
478 CardStartMonth => \( $month ), # MM from MM(-)YY(YY) of 'card_start'
479 CardStartYear => \( $year ), # YY from MM(-)YY(YY) of 'card_start'
483 AccountNumber => 'account_number',
484 ABA => 'routing_code',
485 CheckNumber => 'check_number',
487 DispositionType => 'DispositionType',
488 TotalAmount => 'amount' reformatted into cents
489 FeeAmount => 'FeeAmount',
490 TaxAmount => 'TaxAmount',
491 BillingAddress => 'address',
492 BillingCity => 'city',
493 BillingStateProv => 'state',
494 BillingPostalCode => 'zip',
495 BillingCountry => 'country',
496 BillingPhone => 'phone',
498 UserIPAddr => 'customer_ip',
499 UserHost => 'UserHost',
500 UDField1 => 'UDField1',
501 UDField2 => 'UDField2',
502 UDField3 => 'UDField3',
503 ActionCode => 'ActionCode',
505 Type => 'IndustryInfo',
507 CustomerPO => 'CustomerPO',
508 ShippingMethod => 'ShippingMethod',
509 ShippingName => 'ship_name',
511 Address => 'ship_address',
513 StateProv => 'ship_state',
514 Country => 'ship_country',
515 Phone => 'ship_phone',
521 Business::OnlinePayment::IPPay uses IPPay XML Product Specifications version
524 See http://www.ippay.com/ for more information.
528 Jeff Finucane, ippay@weasellips.com
532 perl(1). L<Business::OnlinePayment>.