File Coverage

blib/lib/ICS/Simple.pm
Criterion Covered Total %
statement 10 166 6.0
branch 0 34 0.0
condition 0 71 0.0
subroutine 4 17 23.5
pod n/a
total 14 288 4.8


line stmt bran cond sub pod time code
1             ##### TODO: expand grammar map
2              
3             package ICS::Simple;
4              
5 1     1   22265 use strict;
  1         3  
  1         51  
6 1     1   6 no strict "refs";
  1         3  
  1         1967  
7              
8             eval "use ICS";
9             die qq{ICS::Simple requires the CyberSource ICS module.\nSee http://www.cybersource.com/support_center/implementation/downloads/scmp_api/.\n} if $@;
10              
11             =head1 NAME
12              
13             ICS::Simple - Simple interface to CyberSource ICS2
14              
15             =head1 VERSION
16              
17             Version 0.06
18              
19             =cut
20              
21             our $VERSION = '0.06';
22              
23             =head1 SYNOPSIS
24              
25             Here is some basic code. Hopefully I'll come back through soon to document it properly.
26              
27             use ICS::Simple;
28            
29             my $ics = ICS::Simple->new(
30             ICSPath => '/opt/ics',
31             MerchantId => 'v0123456789', # CyberSource supplies this number to you
32             Mode => 'test',
33             Currency => 'USD',
34             Grammar => 'UpperCamel', # defaults to raw ICS responses, so you might want to set this
35             #ErrorsTo => 'all-errors@some.fun.place.com',
36             CriticalErrorsTo => 'only-critical-errors@some.fun.place.com',
37             );
38            
39             my $request = {
40             OrderId => 'order19857219',
41             FirstName => 'Fred',
42             LastName => 'Smith',
43             Email => 'fred.smith@buyer-of-stuff.com',
44             CardNumber => '4111111111111111',
45             CardCVV => '123',
46             CardExpYear => '2008',
47             CardExpMonth => '12',
48             BillingAddress => '123 Main St',
49             BillingCity => 'Olympia',
50             BillingRegion => 'WA',
51             BillingPostalCode => '98501',
52             BillingCountryCode => 'US',
53             ShippingAddress1 => '6789 Industrial Pl',
54             ShippingAddress2 => 'Floor 83, Room 11415',
55             ShippingCity => 'Olympia',
56             ShippingRegion => 'WA',
57             ShippingPostalCode => '98506',
58             ShippingCountryCode => 'US',
59             ShippingFee => '25.05',
60             HandlingFee => '5.00',
61             Items => [
62             { Description => 'Mega Lizard Monster RC',
63             Price => '25.00',
64             SKU => 'prod15185' },
65             { Description => 'Super Racer Parts Kit',
66             Price => '15.30',
67             SKU => 'prod23523' },
68             { Description => 'Uber Space Jacket',
69             Price => '72.24',
70             SKU => 'prod18718' },
71             ],
72             };
73            
74             my $response = $ics->requestBill($request);
75            
76             if ($response->{success}) {
77             print "Woo! Success!\n";
78             $response = $response->{response};
79             print "Thanks for your payment of \$$response->{BillAmount}.\n";
80             }
81             else {
82             print "Boo! Failure!\n";
83             print "Error: $response->{error}->{description}\n";
84             }
85              
86             =head1 FUNCTIONS
87              
88             =head2 new
89              
90             =cut
91              
92             sub new {
93 0     0     my $class = shift;
94 0           my %args = @_;
95 0           my $self = {};
96 0           bless($self, $class);
97 0           foreach my $arg (keys(%args)) {
98 0           $self->set($arg, $args{$arg}, $self);
99             }
100 0   0       $self->{server_mode} ||= 'test'; # default to test mode
101 0   0       $self->{icspath} ||= '/opt/ics';
102 0   0       $self->{cvv_accepted} ||= $ICS::Simple::cvvAcceptedDefault;
103 0           return $self;
104             }
105              
106             =head2 set
107              
108             =cut
109              
110             sub set {
111 0     0     my ($self, $key, $val, $namespace, $mapRequired) = @_;
112 0           my $lookupKey = lc($key);
113 0           $lookupKey =~ s|_||g;
114 0 0 0       return undef if ($mapRequired && !$ICS::Simple::fieldMap->{$lookupKey}); # if we require that the name is mapped but it isn't, fail
115 0   0       $key = $ICS::Simple::fieldMap->{$lookupKey} || $key; # if it has a mapped name, use it instead
116 0           $namespace->{$key} = $val;
117 0           return $key;
118             }
119              
120             =head2 requestIcs
121              
122             =cut
123              
124             sub requestIcs {
125 0     0     my $self = shift;
126 0           my $request = shift;
127 0   0       $request->{server_mode} ||= $self->{server_mode};
128 0   0       $request->{server_host} ||= $self->{server_host};
129 0 0         unless ($request->{server_host}) {
130 0 0         if ($request->{server_mode} =~ m|^\s*prod|i) {
131 0           $request->{server_host} = 'ics2.ic3.com';
132 0           $request->{server_port} = '80';
133             } else {
134 0           $request->{server_host} = 'ics2test.ic3.com';
135 0           $request->{server_port} = '80';
136             }
137             }
138 0   0       $request->{currency} ||= 'USD';
139 0   0       $request->{merchant_id} ||= $self->{merchant_id};
140 0   0       $request->{decline_avs_flags} ||= $self->{decline_avs_flags} || $ICS::Simple::avsRejectedDefault;
      0        
141            
142 0           my $response = {};
143 0           %{$response} = ICS::ics_send(%{$request});
  0            
  0            
144            
145 0   0       $ICS::Simple::response->{rmsg} = $response->{auth_rmsg} || $response->{ics_rmsg};
146 0 0         $response->{success} = ($response->{ics_rcode} == 1 ? 1 : 0);
147 0 0         if (!$response->{success}) {
148 0   0       $response->{rmsg} = $ICS::Simple::vitalErrorMap->{$response->{ics_rcode}} || $response->{rmsg};
149 0   0       $response->{auth_auth_error} = $ICS::Simple::vitalErrorMap->{$response->{auth_auth_response}} || $ICS::Simple::vitalErrorMap->{0};
150             }
151            
152 0           my $error = {};
153 0 0         if ($response->{ics_rcode} != 1) { # error
154 0           $error = $self->_handleError($request, $response);
155             }
156            
157             return {
158 0           success => $response->{success},
159             request => $request,
160             response => $self->_translateResponse($response),
161             error => $error
162             };
163             }
164              
165             =head2 _translateResponse
166              
167             =cut
168              
169             sub _translateResponse {
170 0     0     my $self = shift;
171 0           my $response = shift;
172 0   0       my $grammar = shift || $self->{x_grammar};
173 0 0 0       my $gMap = (ref($grammar) eq 'HASH' ? $grammar : ($ICS::Simple::grammarMap->{lc($grammar)} || $ICS::Simple::grammarMap->{stock} || {}));
174 0 0         my $mapRequired = ($gMap->{_mapRequired} ? 1 : 0);
175 0           foreach my $key (keys(%{$response})) {
  0            
176 0           my $val = $response->{$key};
177 0           delete($response->{$key}); # remove old key
178 0 0 0       return undef if ($mapRequired && !$gMap->{$key}); # if we require that the name is mapped but it isn't, fail
179 0   0       $key = $gMap->{$key} || $key; # if it has a mapped name, use it instead
180 0           $response->{$key} = $val; # insert new key
181             }
182 0           return $response;
183             }
184              
185             =head2 _resolveApp
186              
187             =cut
188              
189             sub _resolveApp {
190 0     0     my $self = shift;
191 0           my $appVal = shift;
192 0 0         my @apps = (ref($appVal) eq 'ARRAY' ? @{$appVal} : split(/\s*[,;]+\s*/, $appVal)); # turn it into an array if it isn't one already
  0            
193 0           my @appsResolved;
194 0           foreach my $app (@apps) {
195 0 0         next unless $app; # in case we grabbed an empty one (two delimiters seperated by spaces would cause this)
196 0           my $lookupKey = lc($app);
197 0           $lookupKey =~ s|_||g;
198 0   0       $app = $ICS::Simple::appMap->{$lookupKey} || $app; # if it has a mapped name, use it instead
199 0           push(@appsResolved, $app);
200             }
201 0           return join(',', @appsResolved);
202             }
203              
204             =head2 _constructOffers
205              
206             =cut
207              
208             sub _constructOffers {
209 0     0     my $self = shift;
210 0           my $itemsKey = shift;
211 0           my $namespace = shift;
212 0           my $items = $namespace->{$itemsKey};
213 0 0         my @items = (ref($items) eq 'ARRAY' ? @{$items} : [$items]);
  0            
214 0           my @offers;
215 0           my $i = 0;
216 0           foreach my $item (@items) {
217 0           my @offerAttrs;
218 0           foreach my $attr (keys(%{$item})) {
  0            
219 0           my $val = $item->{$attr};
220 0           my $lookupKey = lc($attr);
221 0           $lookupKey =~ s|_||g;
222 0   0       $attr = $ICS::Simple::offerAttrMap->{$lookupKey} || $attr; # if it has a mapped name, use it instead
223 0           push(@offerAttrs, $attr.':'.$val);
224             }
225 0           my $offer = join('^', 'offerid:'.$i, @offerAttrs);
226 0           $namespace->{'offer'.$i} = $offer;
227 0           $i++;
228             }
229 0           delete($namespace->{$itemsKey});
230             }
231              
232             =head2 request
233              
234             =cut
235              
236             sub request { # convert the request into ICS format and then send it to requestIcs
237 0     0     my $self = shift;
238 0           my $request = shift;
239            
240             # combine input namespaces into one
241 0           my $item = {};
242 0           for my $key (keys(%{$self})) {
  0            
243 0           $self->set($key, $self->{$key}, $item, 1);
244             }
245 0           for my $key (keys(%{$request})) {
  0            
246 0           $self->set($key, $request->{$key}, $item, 1);
247             }
248            
249 0 0         @{$item->{x_items}} = (ref($item->{x_items}) eq 'ARRAY' ? @{$item->{x_items}} : [$item->{x_items}]);
  0            
  0            
250            
251 0 0         if ($item->{x_shipping_handling_fee}) {
252 0           my $offer = {
253             Amount => $item->{x_shipping_handling_fee},
254             ProductCode => 'shipping_and_handling',
255             ProductName => 'Shipping & Handling',
256             };
257 0           delete($item->{x_shipping_handling_fee});
258 0           unshift(@{$item->{x_items}}, $offer);
  0            
259             }
260            
261 0 0         if ($item->{x_shipping_fee}) {
262 0           my $offer = {
263             Amount => $item->{x_shipping_fee},
264             ProductCode => 'shipping_only',
265             ProductName => 'Shipping',
266             };
267 0           delete($item->{x_shipping_fee});
268 0           unshift(@{$item->{x_items}}, $offer);
  0            
269             }
270            
271 0 0         if ($item->{x_handling_fee}) {
272 0           my $offer = {
273             Amount => $item->{x_handling_fee},
274             ProductCode => 'handling_only',
275             ProductName => 'Handling',
276             };
277 0           delete($item->{x_handling_fee});
278 0           unshift(@{$item->{x_items}}, $offer);
  0            
279             }
280            
281 0           $self->_constructOffers('x_items', $item);
282            
283 0           $item->{ics_applications} = $self->_resolveApp($item->{ics_applications});
284 0   0       $item->{decline_avs_flags} = $item->{decline_avs_flags} || $self->{decline_avs_flags} || $ICS::Simple::avsRejectedDefault;
285            
286 0           return $self->requestIcs($item);
287             }
288              
289             =head2 requestBill
290              
291             =cut
292              
293             sub requestBill {
294 0     0     my $self = shift;
295 0           my $request = shift;
296 0           $request->{action} = ['auth', 'bill'];
297 0           return $self->request($request);
298             }
299              
300             =head2 requestAuth
301              
302             =cut
303              
304             sub requestAuth {
305 0     0     my $self = shift;
306 0           my $request = shift;
307 0           $request->{action} = ['auth'];
308 0           return $self->request($request);
309             }
310              
311             =head2 requestAuthReversal
312              
313             =cut
314              
315             sub requestAuthReversal {
316 0     0     my $self = shift;
317 0           my $request = shift;
318 0           $request->{action} = ['authreversal'];
319 0           return $self->request($request);
320             }
321              
322             =head2 requestSettle
323              
324             =cut
325              
326             sub requestSettle {
327 0     0     my $self = shift;
328 0           my $request = shift;
329 0           $request->{action} = ['bill'];
330 0           return $self->request($request);
331             }
332              
333             =head2 requestCredit
334              
335             =cut
336              
337             sub requestCredit {
338 0     0     my $self = shift;
339 0           my $request = shift;
340 0           $request->{action} = ['credit'];
341 0           return $self->request($request);
342             }
343              
344             =head2 _handleError
345              
346             =cut
347              
348             sub _handleError { # it failed, but in a normal kind of way
349 0     0     my $self = shift;
350 0           my $request = shift; # just so we can pass it through to _handleCriticalError
351 0           my $response = shift;
352            
353 0           my $statusCode = lc($response->{ics_rflag});
354 0           $statusCode =~ s|^\s+||;
355 0           $statusCode =~ s|\s+$||;
356            
357 0   0       my $error = $ICS::Simple::errorMap->{$statusCode} || $ICS::Simple::errorMap->{default};
358 0           $error->{statuscode} = $statusCode;
359 0           $error->{data} = $response->{ics_rmsg};
360              
361             # mail the error, if we can
362 0           my $sendErrorsTo = $self->{x_errors_to};
363 0 0 0       if ($error->{critical} || $response->{ics_rcode} < 0) { # critical!
364 0   0       $sendErrorsTo = $self->{x_critical_errors_to} || $sendErrorsTo;
365             }
366 0   0       $self->_sendError('CyberSource Error: '.($error->{name} || $statusCode), $sendErrorsTo, $request, $response, $error);
367            
368 0           return $error;
369             }
370              
371             =head2 _sendError
372              
373             =cut
374              
375             sub _sendError {
376             my $self = shift;
377             my $subject = shift || 'ICS::Simple Error Mail';
378             my $sendTo = shift or return undef;
379             my $request = shift;
380             my $response = shift;
381             my $error = shift;
382              
383 1     1   6701 use Data::Dumper;
  1         10667  
  1         79  
384 1     1   353 use Mail::Send;
  0            
  0            
385            
386             my @mailTo = (ref($sendTo) eq 'ARRAY' ? @{$sendTo} : split(/\s*[,;]+\s*/, $sendTo));
387            
388             my $msg = Mail::Send->new;
389             $msg->to(@mailTo);
390             $msg->subject($subject);
391             $msg->add('X-Site', $self->{x_site});
392             $msg->add('X-IP', $ENV{REMOTE_ADDR});
393            
394             my $body = $msg->open;
395             my $bodyContent = join("\n",
396             '['.gmtime().' UTC]',
397             'ERROR:', Dumper($error),
398             'RESPONSE:', Dumper($response),
399             'REQUEST:', Dumper($request),
400             'ENV:', Dumper(\%ENV),
401             'ICS::Simple:', Dumper($self),
402             );
403            
404             # filter things before we send it out
405             $bodyContent =~ s|(customer_cc_number'.*?'[^']{4})([^']+?)([^']{4}')|$1.('x' x length($2)).$3|ge; # keep the first/last 4 characters, replace all else
406             $bodyContent =~ s|(customer_cc_cv_number'.*?')([^']+?)(')|$1.('x' x length($2)).$3|ge; # replace all the characters
407            
408             print $body $bodyContent;
409             $body->close;
410            
411             return $error;
412             }
413              
414             =head1 AUTHOR
415              
416             Dusty Wilson, C<< >>
417              
418             =head1 BUGS
419              
420             The documentation needs to be finished. Or started. Sorry about that.
421              
422             Please report any bugs or feature requests to
423             C, or through the web interface at
424             L.
425             I will be notified, and then you'll automatically be notified of progress on
426             your bug as I make changes.
427              
428             =head1 SUPPORT
429              
430             You can find documentation for this module with the perldoc command.
431              
432             perldoc ICS::Simple
433              
434             You can also look for information at:
435              
436             =over 4
437              
438             =item * AnnoCPAN: Annotated CPAN documentation
439              
440             L
441              
442             =item * CPAN Ratings
443              
444             L
445              
446             =item * RT: CPAN's request tracker
447              
448             L
449              
450             =item * Search CPAN
451              
452             L
453              
454             =back
455              
456             =head1 ACKNOWLEDGEMENTS
457              
458             =head1 COPYRIGHT & LICENSE
459              
460             Copyright 2006 Dusty Wilson, all rights reserved.
461              
462             This program is free software; you can redistribute it and/or modify it
463             under the same terms as Perl itself.
464              
465             =cut
466              
467             $ICS::Simple::fieldMap = { # not case-sens on the key-side
468             # forced lowercase
469             # stripped underscores
470             host => 'server_host',
471             serverhost => 'server_host',
472             port => 'server_port',
473             serverport => 'server_port',
474             mode => 'server_mode',
475             servermode => 'server_mode',
476             icspath => 'icspath',
477             hostid => 'host_id',
478             merchantid => 'merchant_id',
479             orderid => 'merchant_ref_number',
480             merchantorderid => 'merchant_ref_number',
481             ordernumber => 'merchant_ref_number',
482             merchantordernumber => 'merchant_ref_number',
483             orderrefnumber => 'merchant_ref_number',
484             merchantorderrefnumber => 'merchant_ref_number',
485             refnumber => 'merchant_ref_number',
486             merchantrefnumber => 'merchant_ref_number',
487             merchantdescriptor => 'merchant_descriptor',
488             merchantdesc => 'merchant_descriptor',
489             merchantdescriptorcontact => 'merchant_descriptor_contact',
490             merchantdesccontact => 'merchant_descriptor_contact',
491             currency => 'currency',
492             action => 'ics_applications',
493             actions => 'ics_applications',
494             icsapplications => 'ics_applications',
495             declineavs => 'decline_avs_flags',
496             cvvaccepted => 'cvv_accepted',
497             firstname => 'customer_firstname',
498             givenname => 'customer_firstname',
499             customergivenname => 'customer_firstname',
500             customerfirstname => 'customer_firstname',
501             lastname => 'customer_lastname',
502             familyname => 'customer_lastname',
503             customerfamilyname => 'customer_lastname',
504             customerlastname => 'customer_lastname',
505             email => 'customer_email',
506             customeremail => 'customer_email',
507             billingaddress => 'bill_address1',
508             billingaddress1 => 'bill_address1',
509             billingaddress2 => 'bill_address2',
510             billaddress => 'bill_address1',
511             billaddress1 => 'bill_address1',
512             billaddress2 => 'bill_address2',
513             billingcity => 'bill_city',
514             billcity => 'bill_city',
515             billingregion => 'bill_state',
516             billregion => 'bill_state',
517             billingstateprov => 'bill_state',
518             billstateprov => 'bill_state',
519             billingprov => 'bill_state',
520             billprov => 'bill_state',
521             billingprovince => 'bill_state',
522             billprovince => 'bill_state',
523             billingstate => 'bill_state',
524             billstate => 'bill_state',
525             billingpostalcode => 'bill_zip',
526             billingpostal => 'bill_zip',
527             billpostalcode => 'bill_zip',
528             billpostal => 'bill_zip',
529             billingcountrycode => 'bill_country',
530             billingcountry => 'bill_country',
531             billcountrycode => 'bill_country',
532             billcountry => 'bill_country',
533             shippingaddress => 'ship_to_address1',
534             shippingaddress1 => 'ship_to_address1',
535             shippingaddress2 => 'ship_to_address2',
536             shipaddress => 'ship_to_address1',
537             shipaddress1 => 'ship_to_address1',
538             shipaddress2 => 'ship_to_address2',
539             shiptoaddress => 'ship_to_address1',
540             shiptoaddress1 => 'ship_to_address1',
541             shiptoaddress2 => 'ship_to_address2',
542             shippingcity => 'ship_to_city',
543             shipcity => 'ship_to_city',
544             shiptocity => 'ship_to_city',
545             shippingregion => 'ship_to_state',
546             shipregion => 'ship_to_state',
547             shiptoregion => 'ship_to_state',
548             shippingstateprov => 'ship_to_state',
549             shipstateprov => 'ship_to_state',
550             shiptostateprov => 'ship_to_state',
551             shippingprov => 'ship_to_state',
552             shipprov => 'ship_to_state',
553             shiptoprov => 'ship_to_state',
554             shippingprovince => 'ship_to_state',
555             shipprovince => 'ship_to_state',
556             shiptoprovince => 'ship_to_state',
557             shippingstate => 'ship_to_state',
558             shipstate => 'ship_to_state',
559             shiptostate => 'ship_to_state',
560             shippingpostalcode => 'ship_to_zip',
561             shippingpostal => 'ship_to_zip',
562             shippostalcode => 'ship_to_zip',
563             shippostal => 'ship_to_zip',
564             shiptopostalcode => 'ship_to_zip',
565             shiptopostal => 'ship_to_zip',
566             shippingcountrycode => 'ship_to_country',
567             shippingcountry => 'ship_to_country',
568             shipcountrycode => 'ship_to_country',
569             shipcountry => 'ship_to_country',
570             shiptocountrycode => 'ship_to_country',
571             shiptocountry => 'ship_to_country',
572             cardexpmonth => 'customer_cc_expmo',
573             ccexpmo => 'customer_cc_expmo',
574             customerccexpmo => 'customer_cc_expmo',
575             cardexpyear => 'customer_cc_expyr',
576             ccexpyr => 'customer_cc_expyr',
577             customerccexpyr => 'customer_cc_expyr',
578             cardnumber => 'customer_cc_number',
579             ccnumber => 'customer_cc_number',
580             customerccnumber => 'customer_cc_number',
581             cardcvnumber => 'customer_cc_cv_number',
582             cardcv => 'customer_cc_cv_number',
583             cardcvvnumber => 'customer_cc_cv_number',
584             cardcvv => 'customer_cc_cv_number',
585             cardcvv2 => 'customer_cc_cv_number',
586             cardcvnnumber => 'customer_cc_cv_number',
587             cardcvn => 'customer_cc_cv_number',
588             cccvnnumber => 'customer_cc_cv_number',
589             cccv => 'customer_cc_cv_number',
590             cccvv => 'customer_cc_cv_number',
591             cccvn => 'customer_cc_cv_number',
592             customercccvnnumber => 'customer_cc_cv_number',
593             customercccvvnumber => 'customer_cc_cv_number',
594             customercccvv2number => 'customer_cc_cv_number',
595             customercccvnumber => 'customer_cc_cv_number',
596             ignoreavs => 'ignore_avs',
597             ignorebadcv => 'ignore_bad_cv',
598             timeout => 'timeout',
599             # x_* items are custom (not ICS) fields that are processed before sending
600             appname => 'x_site',
601             app => 'x_site',
602             sitename => 'x_site',
603             site => 'x_site',
604             xsite => 'x_site',
605             errto => 'x_errors_to',
606             errorto => 'x_errors_to',
607             errorsto => 'x_errors_to',
608             xerrorsto => 'x_errors_to',
609             criterrto => 'x_critical_errors_to',
610             criterrorto => 'x_critical_errors_to',
611             criterrorsto => 'x_critical_errors_to',
612             criticalerrto => 'x_critical_errors_to',
613             criticalerrorto => 'x_critical_errors_to',
614             criticalerrorsto => 'x_critical_errors_to',
615             xcriticalerrorsto => 'x_critical_errors_to',
616             grammar => 'x_grammar',
617             shippingfee => 'x_shipping_fee',
618             xshippingfee => 'x_shipping_fee',
619             handlingfee => 'x_handling_fee',
620             xhandlingfee => 'x_handling_fee',
621             shippinghandlingfee => 'x_shipping_handling_fee',
622             xshippinghandlingfee => 'x_shipping_handling_fee',
623             merch => 'x_items',
624             offer => 'x_items',
625             offers => 'x_items',
626             item => 'x_items',
627             items => 'x_items',
628             xitem => 'x_items',
629             xitems => 'x_items',
630             };
631              
632             $ICS::Simple::appMap = { # not case-sens on the key-side
633             # forced lowercase
634             # stripped underscores
635             auth => 'ics_auth',
636             icsauth => 'ics_auth',
637             bill => 'ics_bill',
638             icsbill => 'ics_bill',
639             credit => 'ics_credit',
640             icscredit => 'ics_credit',
641             authreversal => 'ics_auth_reversal',
642             icsauthreversal => 'ics_auth_reversal',
643             authrev => 'ics_auth_reversal',
644             icsauthrev => 'ics_auth_reversal',
645             revauth => 'ics_auth_reversal',
646             icsrevauth => 'ics_auth_reversal',
647             reverseauth => 'ics_auth_reversal',
648             icsreverseauth => 'ics_auth_reversal',
649             score => 'ics_score',
650             icsscore => 'ics_score',
651             export => 'ics_export',
652             icsexport => 'ics_export',
653             dav => 'ics_dav',
654             icsdav => 'ics_dav',
655             };
656              
657             $ICS::Simple::offerAttrMap = { # not case-sens on the key-side
658             # forced lowercase
659             # stripped underscores
660             description => 'product_name',
661             productname => 'product_name',
662             sku => 'merchant_product_sku',
663             itemsku => 'merchant_product_sku',
664             productid => 'merchant_product_sku',
665             itemid => 'merchant_product_sku',
666             merchantproductsku => 'merchant_product_sku',
667             type => 'product_code',
668             producttype => 'product_code',
669             code => 'product_code',
670             productcode => 'product_code',
671             each => 'amount',
672             price => 'amount',
673             amount => 'amount',
674             count => 'quantity',
675             qty => 'quantity',
676             quantity => 'quantity',
677             tax => 'tax_amount',
678             taxamount => 'tax_amount',
679             };
680              
681             $ICS::Simple::grammarMap = {
682             stock => {}, # no change -- raw response
683             lowercamel => {
684             success => 'success',
685             request_token => 'requestToken',
686             request_id => 'requestId',
687             merchant_ref_number => 'orderNumber',
688             rmsg => 'responseMessage',
689             ics_rflag => 'icsResponseFlag',
690             ics_rcode => 'icsResponseCode',
691             ics_rmsg => 'icsResponseMessage',
692             auth_auth_time => 'authTime',
693             auth_rflag => 'authResponseFlag',
694             auth_rcode => 'authResponseCode',
695             auth_rmsg => 'authResponseMessage',
696             auth_auth_response => 'authResponse',
697             auth_auth_error => 'authError',
698             auth_auth_amount => 'authAmount',
699             auth_auth_code => 'authCode',
700             auth_cv_result => 'authCvResult',
701             auth_cv_result_raw => 'authCvRaw',
702             auth_auth_avs => 'authAvsResult',
703             auth_avs_raw => 'authAvsRaw',
704             auth_factor_code => 'authFactorCode',
705             auth_vital_error => 'authVitalError',
706             bill_bill_request_time => 'billRequestTime',
707             bill_rflag => 'billResponseFlag',
708             bill_rcode => 'billResponseCode',
709             bill_rmsg => 'billResponseMessage',
710             bill_trans_ref_no => 'billTransactionNumber',
711             bill_bill_amount => 'billAmount',
712             currency => 'currency',
713             },
714             uppercamel => {
715             success => 'Success',
716             request_token => 'RequestToken',
717             request_id => 'RequestId',
718             merchant_ref_number => 'OrderNumber',
719             rmsg => 'ResponseMessage',
720             ics_rflag => 'IcsResponseFlag',
721             ics_rcode => 'IcsResponseCode',
722             ics_rmsg => 'IcsResponseMessage',
723             auth_auth_time => 'AuthTime',
724             auth_rflag => 'AuthResponseFlag',
725             auth_rcode => 'AuthResponseCode',
726             auth_rmsg => 'AuthResponseMessage',
727             auth_auth_response => 'AuthResponse',
728             auth_auth_error => 'AuthError',
729             auth_auth_amount => 'AuthAmount',
730             auth_auth_code => 'AuthCode',
731             auth_cv_result => 'AuthCvResult',
732             auth_cv_result_raw => 'AuthCvRaw',
733             auth_auth_avs => 'AuthAvsResult',
734             auth_avs_raw => 'AuthAvsRaw',
735             auth_factor_code => 'AuthFactorCode',
736             bill_bill_request_time => 'BillRequestTime',
737             bill_rflag => 'BillResponseFlag',
738             bill_rcode => 'BillResponseCode',
739             bill_rmsg => 'BillResponseMessage',
740             bill_trans_ref_no => 'BillTransactionNumber',
741             bill_bill_amount => 'BillAmount',
742             currency => 'Currency',
743             },
744             };
745              
746             $ICS::Simple::errorMap = {
747             default => {
748             name => 'UNKNOWN',
749             description => 'Unable to process order.',
750             },
751             esystem => {
752             name => 'SYSTEM',
753             description => 'System error.',
754             critical => 1,
755             },
756             etimeout => {
757             name => 'TIMEOUT',
758             description => 'Communications timeout. Please wait a few moments, then try again.',
759             },
760             dduplicate => {
761             name => 'DUPLICATE',
762             description => 'Duplicate transaction refused.',
763             },
764             dinvalid => {
765             name => 'INVALIDDATA',
766             description => 'The provided credit card was declined. Check your billing address, credit card number, and CVV and try again or use a different card.',
767             },
768             dinvaliddata => {
769             name => 'INVALIDDATA',
770             description => 'The provided credit card was declined. Check your billing address, credit card number, and CVV and try again or use a different card.',
771             },
772             dinvalidcard => {
773             name => 'INVALIDCARD',
774             description => 'The provided credit card was declined. Check your billing address, credit card number, and CVV and try again or use a different card.',
775             },
776             dinvalidaddress => {
777             name => 'INVALIDADDRESS',
778             description => 'Invalid address data provided. Please reenter the necessary data and try again.',
779             },
780             dmissingfield => {
781             name => 'MISSINGFIELD',
782             description => 'Required field data missing. Please reenter the necessary data and try again.',
783             },
784             drestricted => {
785             name => 'RESTRICTED',
786             description => 'Unable to process order.',
787             },
788             dcardrefused => {
789             name => 'CARDREFUSED',
790             description => 'The provided credit card was declined. Check your billing address, credit card number, and CVV and try again or use a different card.',
791             },
792             dcall => {
793             name => 'CALL',
794             description => 'Unable to process order.',
795             },
796             dcardexpired => {
797             name => 'CARDEXPIRED',
798             description => 'The provided credit card is expired. Please use another card and try again.',
799             },
800             davsno => {
801             name => 'AVSFAILED',
802             description => 'The provided credit card was declined. Check your billing address, credit card number, and CVV and try again or use a different card.',
803             },
804             dcv => {
805             name => 'CV',
806             description => 'The provided credit card was declined. Check your billing address, credit card number, and CVV and try again or use a different card.',
807             },
808             dnoauth => {
809             name => 'NOAUTH',
810             description => 'The requested transaction does not match a valid, existing authorization transaction.',
811             },
812             dscore => {
813             name => 'SCORE',
814             description => 'Score exceeds limit.',
815             },
816             dsettings => {
817             name => 'SETTINGS',
818             description => 'The provided credit card was declined. Check your billing address, credit card number, and CVV and try again or use a different card.',
819             },
820             };
821              
822             $ICS::Simple::cvvAcceptedDefault = 'M,P,U,X,1';
823              
824             $ICS::Simple::cvvMap = {
825             I => {
826             note => 'Card verification number failed processor\'s data validation check.',
827             },
828             M => {
829             note => 'Card verification number matched.',
830             },
831             N => {
832             note => 'Card verification number not matched.',
833             },
834             P => {
835             note => 'Card verification number not processed.',
836             },
837             S => {
838             note => 'Card verification number is on the card but was not included in the request.',
839             },
840             U => {
841             note => 'Card verification is not supported by the issuing bank.',
842             },
843             X => {
844             note => 'Card verification is not supported by the card association.',
845             },
846             1 => {
847             note => 'CyberSource does not support card verification for this processor or card type.',
848             },
849             2 => {
850             note => 'The processor returned an unrecognized value for the card verification response.',
851             },
852             3 => {
853             note => 'The processor did not return a card verification result code.',
854             },
855             };
856              
857             $ICS::Simple::avsRejectedDefault = 'A,C,E,I,N,R,S,U,W,1,2';
858              
859             $ICS::Simple::avsMap = {
860             A => {
861             note => 'Street address matches, but both 5-digit ZIP code and 9-digit ZIP code do not match.',
862             },
863             B => {
864             note => 'Street address matches, but postal code not verified. Returned only for non-U.S.-issued Visa cards.',
865             },
866             C => {
867             note => 'Street address and postal code do not match. Returned only for non U.S.-issued Visa cards.',
868             },
869             D => {
870             note => 'Street address and postal code both match. Returned only for non-U.S.-issued Visa cards.',
871             },
872             E => {
873             note => 'AVS data is invalid.',
874             },
875             G => {
876             note => 'Non-U.S. issuing bank does not support AVS.',
877             },
878             I => {
879             note => 'Address information not verified. Returned only for non-U.S.-issued Visa cards.',
880             },
881             J => {
882             note => 'Card member name, billing address, and postal code all match. Ship-to information verified and chargeback protection guaranteed through the Fraud Protection Program. This code is returned only if you are signed up to use AAV+ with the American Express Phoenix processor.',
883             },
884             K => {
885             note => 'Card member\'s name matches. Both billing address and billing postal code do not match. This code is returned only if you are signed up to use Enhanced AVS or AAV+ with the American Express Phoenix processor.',
886             },
887             L => {
888             note => 'Card member\'s name matches. Billing postal code matches, but billing address does not match. This code is returned only if you are signed up to use Enhanced AVS or AAV+ with the American Express Phoenix processor.',
889             },
890             M => {
891             note => 'Street address and postal code both match. Returned only for non-U.S.-issued Visa cards.',
892             },
893             N => {
894             note => 'Street address and postal code do not match. Returned only for U.S.-issued Visa cards.',
895             },
896             O => {
897             note => 'Card member name matches. Billing address matches, but billing postal code does not match. This code is returned only if you are signed up to use Enhanced AVS or AAV+ with the American Express Phoenix processor.',
898             },
899             P => {
900             note => 'Postal code matches, but street address not verified. Returned only for non-U.S.-issued Visa cards.',
901             },
902             Q => {
903             note => 'Card member name, billing address, and postal code all match. Ship-to information verified but chargeback protection not guaranteed (Standard program). This code is returned only if you are signed up to use AAV+ with the American Express Phoenix processor.',
904             },
905             R => {
906             note => 'System unavailable.',
907             },
908             S => {
909             note => 'U.S. issuing bank does not support AVS.',
910             },
911             U => {
912             note => 'Address information unavailable. Returned if non-U.S. AVS is not available or if the AVS in a U.S. bank is not functioning properly.',
913             },
914             V => {
915             note => 'Card member name matches. Both billing address and billing postal code match. This code is returned only if you are signed up to use Enhanced AVS or AAV+ with the American Express Phoenix processor.',
916             },
917             W => {
918             note => 'Street address does not match, but 9-digit ZIP code matches.',
919             },
920             X => {
921             note => 'Exact match. Street address and 9-digit ZIP code both match.',
922             },
923             Y => {
924             note => 'Street address and 5-digit ZIP code both match.',
925             },
926             Z => {
927             note => 'Street address does not match, but 5-digit ZIP code matches.',
928             },
929             1 => {
930             note => 'CyberSource AVS code. AVS is not supported for this processor or card type.',
931             },
932             2 => {
933             note => 'CyberSource AVS code. The processor returned an unrecognized value for the AVS response.',
934             },
935             };
936              
937             $ICS::Simple::vitalErrorMap = {
938             '01' => {
939             action => 'decline',
940             note => 'Refer to Issuer',
941             description => 'Please call your card issuer.',
942             },
943             '02' => {
944             action => 'decline',
945             note => 'Refer to Issuer - Special Condition',
946             description => 'Please call your card issuer.',
947             },
948             '03' => {
949             action => 'error',
950             note => 'Invalid Merchant ID',
951             description => 'Please call customer service to complete order.',
952             },
953             '04' => {
954             action => 'decline',
955             note => 'Pick up card',
956             description => 'Authorization has been declined.',
957             },
958             '05' => {
959             action => 'decline',
960             note => 'Do Not Honor',
961             description => 'Authorization has been declined.',
962             },
963             '06' => {
964             action => 'error',
965             note => 'General Error',
966             description => 'Please call customer service to complete order.',
967             },
968             '07' => {
969             action => 'decline',
970             note => 'Pick up card - Special Condition',
971             description => 'Authorization has been declined.',
972             },
973             '12' => {
974             action => 'error',
975             note => 'Unknown system error',
976             description => 'Please call customer service to complete order.',
977             },
978             '13' => {
979             action => 'error',
980             note => 'Invalid Amount',
981             description => 'Invalid amount.',
982             },
983             '14' => {
984             action => 'error',
985             note => 'Invalid Card Number',
986             description => 'Invalid card number.',
987             },
988             '15' => {
989             action => 'error',
990             note => 'No such issuer',
991             description => 'Invalid card number.',
992             },
993             '19' => {
994             action => 'decline',
995             note => 'Unknown Decline Error',
996             description => 'Authorization has been declined.',
997             },
998             '28' => {
999             action => 'error',
1000             note => 'File is temporarily unavailable',
1001             description => 'Please call customer service to complete order.',
1002             },
1003             '39' => {
1004             action => 'error',
1005             note => 'Invalid Card Number',
1006             description => 'Invalid card number.',
1007             },
1008             '41' => {
1009             action => 'decline',
1010             note => 'Pick up card - Lost',
1011             description => 'Authorization has been declined.',
1012             },
1013             '43' => {
1014             action => 'decline',
1015             note => 'Pick up card - Stolen',
1016             description => 'Authorization has been declined.',
1017             },
1018             '51' => {
1019             action => 'decline',
1020             note => 'Insufficient Funds',
1021             description => 'Authorization has been declined.',
1022             },
1023             '52' => {
1024             action => 'error',
1025             note => 'Unknown Card Number Error',
1026             description => 'Invalid Card Number',
1027             },
1028             '53' => {
1029             action => 'error',
1030             note => 'Unknown Card Number Error',
1031             description => 'Invalid Card Number',
1032             },
1033             '54' => {
1034             action => 'expired',
1035             note => 'Expired Card',
1036             description => 'Expired card.',
1037             },
1038             '55' => {
1039             action => 'error',
1040             note => 'Incorrect PIN',
1041             description => 'Incorrect Card/PIN combination.', # is it safe to let them know the PIN is wrong?
1042             },
1043             '57' => {
1044             action => 'decline',
1045             note => 'Transaction Disallowed - Card',
1046             description => 'Authorization has been declined.',
1047             },
1048             '58' => {
1049             action => 'decline',
1050             note => 'Transaction Disallowed - Term',
1051             description => 'Authorization has been declined.',
1052             },
1053             '61' => {
1054             action => 'decline',
1055             note => 'Withdrawal Limit Exceeded',
1056             description => 'Withdrawal limit exceeded.',
1057             },
1058             '62' => {
1059             action => 'decline',
1060             note => 'Invalid Service Code, Restricted',
1061             description => 'Authorization has been declined.',
1062             },
1063             '63' => {
1064             action => 'decline',
1065             note => 'Unknown Decline Error',
1066             description => 'Authorization has been declined.',
1067             },
1068             '65' => {
1069             action => 'decline',
1070             note => 'Activity Limit Exceeded',
1071             description => 'Authorization has been declined.',
1072             },
1073             '75' => {
1074             action => 'decline',
1075             note => 'PIN Attempts Exceeded',
1076             description => 'Authorization has been declined.',
1077             },
1078             '78' => {
1079             action => 'error',
1080             note => 'Unknown Invalid Card Number Error',
1081             description => 'Invalid Card Number',
1082             },
1083             '79' => {
1084             action => 'call',
1085             note => 'Unknown Error',
1086             description => 'Please call customer service to complete order.',
1087             },
1088             '80' => {
1089             action => 'error',
1090             note => 'Invalid Expiration Date',
1091             description => 'Invalid expiration date.',
1092             },
1093             '82' => {
1094             action => 'decline',
1095             note => 'Cashback Limit Exceeded',
1096             description => 'Cashback limit exceeded.',
1097             },
1098             '83' => {
1099             action => 'decline',
1100             note => 'Unknown PIN Verification Error',
1101             description => 'Can not verify PIN.',
1102             },
1103             '86' => {
1104             action => 'decline',
1105             note => 'Unknown PIN Verification Error',
1106             description => 'Can not verify PIN.',
1107             },
1108             '91' => {
1109             action => 'unavailable',
1110             note => 'Issuer or switch is unavailable',
1111             description => 'Please call customer service to complete order.',
1112             },
1113             '92' => {
1114             action => 'call',
1115             note => 'Unknown Error',
1116             description => 'Please call customer service to complete order.',
1117             },
1118             '93' => {
1119             action => 'decline',
1120             note => 'Violation, Cannot Complete',
1121             description => 'Authorization has been declined.',
1122             },
1123             '96' => {
1124             action => 'error',
1125             note => 'System Malfunction',
1126             description => 'Please call customer service to complete order.',
1127             },
1128             'EA' => {
1129             action => 'error',
1130             note => 'Verification Error',
1131             description => 'Please call customer service to complete order.',
1132             },
1133             'EB' => {
1134             action => 'call',
1135             note => 'Unknown Error',
1136             description => 'Please call customer service to complete order.',
1137             },
1138             'EC' => {
1139             action => 'error',
1140             note => 'Verification Error',
1141             description => 'Please call customer service to complete order.',
1142             },
1143             'N3' => {
1144             action => 'error',
1145             note => 'Cashback Service Not Available',
1146             description => 'Cashback service is not available.',
1147             },
1148             'N4' => {
1149             action => 'decline',
1150             note => 'Issuer Withdrawal Limit Exceeded',
1151             description => 'Amount exceeds issuer withdrawal limit.',
1152             },
1153             'N7' => {
1154             action => 'error',
1155             note => 'Invalid CVV2 Data Supplied',
1156             description => 'Invalid card security code.', # should we let the enduser know this?
1157             },
1158             };
1159              
1160             1; # End of ICS::Simple