File Coverage

blib/lib/Business/OnlinePayment/WorldPay.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::WorldPay;
2              
3 1     1   39424 use 5.008008;
  1         4  
  1         54  
4 1     1   6 use strict;
  1         3  
  1         33  
5 1     1   6 use warnings;
  1         14  
  1         27  
6 1     1   6 use Carp;
  1         1  
  1         128  
7              
8 1     1   854 use Template; # construct XML requests
  1         1055105  
  1         33  
9 1     1   509 use XML::TreeBuilder; # parse XML responses
  0            
  0            
10              
11             use Net::SSLeay qw(post_https make_headers); # submit requests
12             use MIME::Base64; # basic authentication
13              
14             use base qw(Exporter Business::OnlinePayment); # Exporter just for $VERSION checking
15              
16              
17             our $VERSION = '1.00';
18             our $DEBUG_FH = \*STDERR; # debugging output destination
19              
20              
21             # From the "Submitting Transactions in the Direct Model" document
22             # and the WorldPay DTD: http://dtd.worldpay.com/paymentService_v1.dtd
23              
24             my %Payment_Method = (
25             'visa' => {
26             paymentType => 'VISA-SSL',
27             _required => [ qw/card_number exp_date name/ ],
28             },
29              
30             'amex' => {
31             paymentType => 'AMEX-SSL',
32             _required => [ qw/card_number exp_date name/ ],
33             },
34              
35             'mastercard' => {
36             paymentType => 'ECMC-SSL',
37             _required => [ qw/card_number exp_date name/ ],
38             },
39              
40             'diners card' => {
41             paymentType => 'DINERS-SSL',
42             _required => [ qw/card_number exp_date name/ ],
43             },
44              
45             'solo card' => {
46             paymentType => 'SOLO_GB-SSL',
47             _required => [ qw/card_number exp_date name/ ],
48              
49             # from DTD: (issueNumber | (startDate, issueNumber?) )
50             # either issue number or start date must be included
51             # have to handle as a special case in sub submit
52             },
53              
54             'maestro' => {
55             paymentType => 'MAESTRO-SSL',
56             _required => [ qw/card_number exp_date name/ ],
57              
58             # from DTD: startDate?, issueNumber?
59             # issue number and start date both optional (?)
60             },
61              
62             'elv' => {
63             paymentType => 'ELV-SSL',
64             _required => [ qw/account_holder_name bank_account_number bank_name
65             bank_location bank_location_id/ ],
66             },
67              
68             'jcb card' => {
69             paymentType => 'JCB-SSL',
70             _required => [ qw/card_number exp_date name/ ],
71             },
72             );
73              
74             $Payment_Method{diners} = $Payment_Method{'diners card'};
75             $Payment_Method{jcb} = $Payment_Method{'jcb card'};
76             $Payment_Method{solo} = $Payment_Method{'solo card'};
77              
78              
79             my %Server = (
80             live => {
81             server => 'secure.ims.worldpay.com',
82             path => '/jsp/merchant/xml/paymentService.jsp',
83             port => 443,
84             },
85             test => {
86             server => 'secure-test.wp3.rbsworldpay.com',
87             path => '/jsp/merchant/xml/paymentService.jsp',
88             port => 443,
89             },
90             );
91              
92              
93             sub debug
94             {
95             my ($self, $value, $filename) = @_;
96              
97             $self->{debug} = $value if defined $value;
98              
99             if ( $filename ) {
100             if (open(my $fh, ">>", $filename)) {
101             my $old_fh = select($fh);
102             $| = 1; # output autoflush
103             select($old_fh);
104             $DEBUG_FH = $fh;
105             }
106             else {
107             carp "cannot open debugging file $filename: $!\n";
108             }
109             }
110              
111             return $self->{debug};
112             }
113              
114              
115             sub set_server
116             {
117             my ($self, $server_type) = @_;
118              
119             $self->{'_server'} = $server_type; # 'live' or 'test'
120              
121             $self->server( $Server{$server_type}->{'server'} );
122             $self->path( $Server{$server_type}->{'path'} );
123             $self->port( $Server{$server_type}->{'port'} );
124             }
125              
126              
127             sub set_defaults # called by B::OP constructor
128             {
129             my $self = shift;
130              
131             $self->{'_content'} = {};
132              
133             _launder_envariables(); # clean up WORLDPAY_* variables
134              
135             # B::OP creates the following accessors:
136             # server, port, path, test_transaction, transaction_type,
137             # server_response, is_success, authorization,
138             # result_code, error_message,
139              
140             # let's create some more:
141              
142             $self->build_subs(
143             qw/
144             installation login password version currency action
145             cvv_response avs_response risk_score last_event
146             /
147             );
148              
149             $self->test_transaction(0); # default to live server
150              
151             $self->version('1.4'); # paymentService "version" attribute
152              
153             $self->action('payment'); # default action is payment
154              
155             $self->currency('EUR'); # default value for currencyCode
156              
157             if ($ENV{WORLDPAY_DEBUG}) {
158             if ($ENV{WORLDPAY_DEBUG} =~ /^\d+$/) {
159             $self->debug( 1 );
160             }
161             else {
162             $self->debug( 1, $ENV{WORLDPAY_DEBUG} );
163             }
164             }
165             }
166              
167              
168             sub submit
169             {
170             my $self = shift;
171              
172             my %content = $self->content;
173              
174             if ($content{action}) {
175             $content{action} = 'payment'
176             if $content{action} =~ /^ \s* normal \s+ authori[zs]ation \s* $/ix;
177             $self->action( lc $content{action} );
178             }
179              
180             ################ initialize object ################
181              
182             # standard B::OP attributes
183             $self->$_( '' ) for qw/is_success
184             authorization
185             result_code
186             error_message
187             server
188             port
189             path
190             server_response/;
191              
192             $self->transaction_type( '' ) unless $self->transaction_type;
193              
194             # additional B::OP::WorldPay attributes
195             $self->$_( '' ) for qw/last_event
196             cvv_response
197             avs_response
198             risk_score/;
199              
200             $self->set_server( $self->test_transaction ? 'test' : 'live' );
201              
202             $self->installation( $content{installation} || $ENV{WORLDPAY_INSTALLATION_ID} );
203             $self->login( $content{login} || $ENV{WORLDPAY_MERCHANT_CODE} );
204             $self->password( $content{password} || $ENV{WORLDPAY_XML_PASSWORD} );
205              
206             if ($self->debug) {
207             print $DEBUG_FH "\n";
208             print $DEBUG_FH '*' x 80, "\n\n";
209             print $DEBUG_FH "installation: ", $self->installation, "\n";
210             print $DEBUG_FH "login: ", $self->login, "\n";
211             print $DEBUG_FH "password: ", $self->password, "\n";
212             }
213              
214             ##### get template & variables for xml request ####
215              
216             my ($xml_template, $template_vars);
217              
218             my $action = $self->action;
219              
220             if ($action eq 'status' || $action eq 'cancel' || $action eq 'cancel_or_refund') {
221             ($xml_template, $template_vars) = $self->status_inquiry_or_cancel;
222             }
223             elsif ( $action eq 'refund' ) {
224             ($xml_template, $template_vars) = $self->refund;
225             }
226             elsif ($Payment_Method{lc $content{type}}{paymentType} eq 'ELV-SSL') {
227             ($xml_template, $template_vars) = $self->elv_payment;
228             }
229             else {
230             ($xml_template, $template_vars) = $self->payment;
231             }
232              
233             ############### create XML request ################
234              
235             my $post_data_xml;
236              
237             my $tt = Template->new();
238              
239             $tt->process( \$xml_template, $template_vars, \$post_data_xml ) ||
240             croak $tt->error();
241              
242             if ($self->debug) {
243             print $DEBUG_FH "\n", "=" x 80, "\n";
244             print $DEBUG_FH " " x 35, "REQUEST:\n";
245             print $DEBUG_FH "=" x 80, "\n\n";
246             print $DEBUG_FH "post_data_xml = \n$post_data_xml\n";
247             print $DEBUG_FH "POST_HTTPS: ", $self->server . $self->path, "\n";
248             }
249              
250             ################# submit request ##################
251              
252             my ($page, $response, %headers) = post_https(
253             $self->server,
254             $self->port,
255             $self->path,
256             make_headers(
257             Authorization => 'Basic ' . MIME::Base64::encode(
258             $self->login . ":" . $self->password, ''
259             )
260             ),
261             $post_data_xml,
262             );
263              
264             ################ examine response #################
265              
266             if ($self->debug) {
267             my $prettier_xml;
268             ($prettier_xml = $page) =~ s/>\n
269             print $DEBUG_FH "\n", "=" x 80, "\n";
270             print $DEBUG_FH " " x 35, "RESPONSE:\n";
271             print $DEBUG_FH "=" x 80, "\n\n";
272              
273             print $DEBUG_FH "$response\n\n";
274             print $DEBUG_FH "HEADERS:\n";
275             print $DEBUG_FH " $_: $headers{$_}\n" for sort keys %headers;
276             print $DEBUG_FH "\nXML:\n$prettier_xml\n";
277             print $DEBUG_FH "-" x 80, "\n\n";
278             }
279              
280             $self->server_response( $response );
281              
282             if ($self->server_response =~ /\b401 Authorization Required/) {
283             $self->is_success(0);
284             $self->error_message('Authorization Required');
285             return;
286             }
287              
288             unless ($page && $self->server_response =~ /\b200 OK/) {
289             $self->is_success(0);
290             $self->error_message(
291             'There was a problem communicating with the payment server. ' .
292             'Please try again.'
293             );
294             return;
295             }
296              
297             ############### parse XML response ################
298              
299             my $xml = XML::TreeBuilder->new;
300             $xml->parse($page);
301              
302             if ($self->debug) {
303             print $DEBUG_FH "XML::TreeBuilder dump:\n";
304             $xml->dump($DEBUG_FH);
305             print $DEBUG_FH "\n";
306             }
307              
308             # structure of the XML response varies
309              
310             my (
311             $error, $last_event, $return_code, $cvc_result, $avs_response, $risk, $ok,
312             );
313              
314             # response for various errors:
315             if ($error = $xml->find('error')) {
316             $self->is_success(0);
317             $self->authorization( 'ERROR' );
318             $self->error_message( $error->as_text );
319             }
320              
321             # response for payment requests or status inquiries:
322             elsif ($last_event = $xml->find('lastEvent')) { # response for payments
323              
324             $self->last_event( $last_event->as_text );
325              
326             if ($last_event->as_text eq 'AUTHORISED') { # UK spelling
327             $self->is_success(1);
328             $self->authorization( 'AUTHORIZED' ); # switching to US spelling
329             $self->last_event( 'AUTHORIZED' );
330             }
331              
332             elsif ($last_event->as_text eq 'REFUSED') {
333             if ($return_code = $xml->find('ISO8583ReturnCode')) {
334             $self->is_success(0);
335             $self->authorization( 'REFUSED' );
336             $self->result_code( $return_code->attr('code') );
337             $self->error_message( $return_code->attr('description') );
338             }
339             }
340              
341             # if needed, can provide special handling for other last_event values:
342             # CANCELLED, CAPTURED, SETTLED, SETTLED_BY_MERCHANT,
343             # SENT_FOR_REFUND, REFUNDED_BY_MERCHANT, CHARGED_BACK
344             #
345             # elsif ($last_event->as_text eq 'XXX') {
346             # }
347              
348             else {
349             $self->is_success(1);
350             }
351              
352             # get CVCResultCode description if present
353              
354             if ($cvc_result = $xml->find('CVCResultCode')) {
355             $self->cvv_response( $cvc_result->attr('description') );
356             }
357              
358             # get AVSResultCode description if present
359              
360             if ($avs_response = $xml->find('AVSResultCode')) {
361             $self->avs_response( $avs_response->attr('description') );
362             }
363              
364             # get riskScore if present
365              
366             if ($risk = $xml->find('riskScore')) {
367             $self->risk_score( $risk->attr('value') );
368             }
369              
370             }
371              
372             # response for cancel request:
373             elsif ( $ok = $xml->find('ok') and $ok->find('cancelReceived') ) {
374             $self->is_success(1);
375             $self->authorization( 'AUTHORIZED' );
376             }
377              
378             # response for refund request:
379             elsif ( $ok = $xml->find('ok') and $ok->find('refundReceived') ) {
380             $self->is_success(1);
381             $self->authorization( 'AUTHORIZED' );
382             }
383              
384             # response for UNDOCUMENTED cancelOrRefund request:
385             elsif ( $ok = $xml->find('ok') and $ok->find('voidReceived') ) {
386             $self->is_success(1);
387             $self->authorization( 'AUTHORIZED' );
388             }
389              
390             $xml->delete;
391              
392             if ($self->debug) {
393             print $DEBUG_FH "\n", "=" x 80, "\n";
394             print $DEBUG_FH " " x 35, "RESULTS:\n";
395             print $DEBUG_FH "=" x 80, "\n\n";
396            
397             print $DEBUG_FH "Standard B::OP attributes:\n";
398            
399             print $DEBUG_FH "test_transaction = ", $self->test_transaction, "\n";
400             print $DEBUG_FH "transaction_type = ", $self->transaction_type, "\n";
401             print $DEBUG_FH "is_success = ", $self->is_success, "\n";
402             print $DEBUG_FH "authorization = ", $self->authorization, "\n";
403             print $DEBUG_FH "result_code = ", $self->result_code, "\n";
404             print $DEBUG_FH "error_message = ", $self->error_message, "\n";
405             print $DEBUG_FH "server = ", $self->server, "\n";
406             print $DEBUG_FH "port = ", $self->port, "\n";
407             print $DEBUG_FH "path = ", $self->path, "\n";
408             print $DEBUG_FH "server_response = ", $self->server_response, "\n\n";
409            
410             print $DEBUG_FH "Additional B::OP::WorldPay attributes:\n";
411            
412             print $DEBUG_FH "cvv_response = ", $self->cvv_response, "\n";
413             print $DEBUG_FH "avs_response = ", $self->avs_response, "\n";
414             print $DEBUG_FH "risk_score = ", $self->risk_score, "\n\n";
415             print $DEBUG_FH "last_event = ", $self->last_event, "\n\n";
416            
417             print $DEBUG_FH "-" x 80, "\n\n";
418             }
419             }
420              
421              
422             sub payment
423             {
424             my $self = shift;
425              
426             my %content = $self->content;
427              
428             $self->required_fields( 'type' );
429              
430             croak("unrecognized credit card type: $content{type}\n") unless $Payment_Method{lc $content{type}};
431              
432             if ($content{type} =~ /^solo/i) {
433             croak("missing required field issue_number or start_date")
434             unless exists $content{issue_number} || exists $content{start_date};
435             }
436              
437             my @required_fields = @{ $Payment_Method{ lc $content{type} }{_required} };
438              
439             $self->required_fields( @required_fields );
440              
441             ########### initialize template variables #########
442              
443             $self->currency( $content{currency} ) if $content{currency};
444             my $currency = $self->currency;
445              
446             my $amount_value = $content{amount};
447              
448             # amount must be specified with an "exponent"
449             # Example: $123.45 would be specified as value = "12345", exponent = "2"
450              
451             my $amount_exponent = 2; # works for all but Indonesian Rupiah
452              
453             if ($amount_value) {
454             $amount_value =~ s/,(?=\d\d$)/./g; # 123,45 => 123.45
455             $amount_value =~ s/,//g; # 12,345.46 => 12345.46
456             $amount_value = sprintf "%.2f", $amount_value; # 123 => 123.00
457             $amount_value =~ s/\.//g; # 123.00 => 12300
458             }
459              
460             # strip non-digits from card number
461             my $card_number = '';
462             if ( $content{card_number} ) {
463             ( $card_number = $content{card_number} ) =~ s/\D//g;
464             }
465              
466             # separate month and year values for expiryDate
467              
468             my ($exp_month, $exp_year);
469             if ( $content{exp_date} ) {
470             ($exp_month, $exp_year) = split /\//, $content{exp_date};
471             $exp_month = sprintf "%02d", $exp_month;
472             $exp_year = $exp_year < 100 ? 2000 + $exp_year : $exp_year; # Y3K problem
473             }
474              
475             # Solo & Maestro cards may have startDate
476             # separate month and year values for startDate
477              
478             my ($start_month, $start_year);
479             if ($content{start_date}) {
480             ($start_month, $start_year) = split /\//, $content{start_date};
481             $start_month = sprintf "%02d", $start_month;
482             $start_year = $start_year < 100 ? 2000 + $start_year : $start_year; # Y3K problem
483             }
484              
485             # get first and last names from cardHolderName
486              
487             my ($first_name, $last_name);
488             if ($content{name}) {
489             my @names = split ' ', $content{name};
490             $last_name = pop @names;
491             $first_name = join ' ' => @names;
492             }
493              
494             # WorldPay suggests putting the whole address in the street field
495             # with the exception of the postal and country codes
496              
497             my @address;
498             push @address, $content{address} if $content{address};
499             push @address, $content{city} if $content{city};
500             push @address, $content{state} if $content{state};
501             my $street = join ', ' => @address;
502              
503             ########### get XML template for request ##########
504              
505             my $xml_template = _get_xml_template( $self->action );
506              
507             ########### initialize data for template ##########
508              
509             my $template_vars = {
510              
511             merchantCode => $self->login,
512              
513             version => $self->version,
514              
515             orderCode => $content{order_number},
516              
517             installationId => $self->installation,
518              
519             description => $content{description},
520              
521             amount => {
522             value => $amount_value,
523             currencyCode => $self->currency,
524             exponent => $amount_exponent,
525             },
526              
527             paymentDetails => {
528             paymentType => $Payment_Method{lc $content{type}}{paymentType},
529             action => 'AUTHORISE',
530             cardNumber => $card_number,
531             expiryDate => {
532             month => $exp_month,
533             year => $exp_year,
534             },
535             cardHolderName => $content{name},
536             issueNumber => $content{issue_number},
537             startDate => {
538             month => $start_month,
539             year => $start_year,
540             },
541             cvc => $content{cvc},
542             cardAddress => {
543             firstName => $content{first_name},
544             lastName => $content{last_name},
545             street => $street,
546             postalCode => $content{zip},
547             countryCode => $content{country},
548             telephoneNumber => $content{phone},
549             },
550             },
551              
552             # required for 3D secure authentication
553             session => {
554             shopperIPAddress => $content{ip_address},
555             id => $content{session_id},
556             },
557              
558             # required for 3D secure authentication
559             shopper => {
560             acceptHeader => $content{accept_header},
561             userAgentHeader => $content{user_agent},
562             },
563              
564             };
565              
566             return ($xml_template, $template_vars);
567             }
568              
569              
570             sub elv_payment
571             {
572             my $self = shift;
573              
574             my %content = $self->content;
575              
576             $self->required_fields( 'type' );
577              
578             my @required_fields = @{ $Payment_Method{ lc $content{type} }{_required} };
579              
580             $self->required_fields( @required_fields );
581              
582             ########### initialize template variables #########
583              
584             $self->currency( $content{currency} ) if $content{currency};
585             my $currency = $self->currency;
586              
587             my $amount_value = $content{amount};
588              
589             # amount must be specified with an "exponent"
590             # Example: $123.45 would be specified as value = "12345", exponent = "2"
591              
592             my $amount_exponent = 2; # works for all but Indonesian Rupiah
593              
594             if ($amount_value) {
595             $amount_value =~ s/,(?=\d\d$)/./g; # 123,45 => 123.45
596             $amount_value =~ s/,//g; # 12,345.46 => 12345.46
597             $amount_value = sprintf "%.2f", $amount_value; # 123 => 123.00
598             $amount_value =~ s/\.//g; # 123.00 => 12300
599             }
600              
601             # strip non-digits from bank account number
602             my $bank_account_number = '';
603             if ( $content{bank_account_number} ) {
604             ( $bank_account_number = $content{bank_account_number} ) =~ s/\D//g;
605             }
606              
607             ########### get XML template for request ##########
608              
609             my $xml_template = _get_xml_template( $self->action, 'ELV-SSL' );
610              
611             ########### initialize data for template ##########
612              
613             my $template_vars = {
614              
615             merchantCode => $self->login,
616              
617             version => $self->version,
618              
619             orderCode => $content{order_number},
620              
621             installationId => $self->installation,
622              
623             description => $content{description},
624              
625             amount => {
626             value => $amount_value,
627             currencyCode => $self->currency,
628             exponent => $amount_exponent,
629             },
630              
631             paymentDetails => {
632             paymentType => $Payment_Method{lc $content{type}}{paymentType},
633             action => 'AUTHORISE',
634             bankAccountNr => $bank_account_number,
635             bankName => $content{bank_name},
636             accountHolderName => $content{account_holder_name},
637             bankLocation => $content{bank_location},
638             bankLocationId => $content{bank_location_id},
639             },
640              
641             # required for 3D secure authentication
642             session => {
643             shopperIPAddress => $content{ip_address},
644             id => $content{session_id},
645             },
646              
647             # required for 3D secure authentication
648             shopper => {
649             acceptHeader => $content{accept_header},
650             userAgentHeader => $content{user_agent},
651             },
652              
653             };
654              
655             return ($xml_template, $template_vars);
656             }
657              
658              
659             sub status_inquiry_or_cancel
660             {
661             my $self = shift;
662              
663             my %content = $self->content;
664              
665             $self->required_fields( qw/order_number/ );
666              
667             ########### get XML template for request ##########
668              
669             my $xml_template = _get_xml_template( $self->action );
670              
671             ########### initialize data for template ##########
672              
673             my $template_vars = {
674             merchantCode => $self->login,
675             version => $self->version,
676             orderCode => $content{order_number},
677             };
678              
679             return ($xml_template, $template_vars);
680             }
681              
682              
683             sub refund
684             {
685             my $self = shift;
686              
687             my %content = $self->content;
688              
689             $self->required_fields( qw/order_number amount currency/ );
690              
691             ########### initialize template variables #########
692              
693             $self->currency( $content{currency} ) if $content{currency};
694             my $currency = $self->currency;
695              
696             my $amount_value = $content{amount};
697              
698             # amount must be specified with an "exponent"
699             # Example: $123.45 would be specified as value = "12345", exponent = "2"
700              
701             my $amount_exponent = 2; # works for all but Indonesian Rupiah
702              
703             $amount_value =~ s/,(?=\d\d$)/./g; # 123,45 => 123.45
704             $amount_value =~ s/,//g; # 12,345.46 => 12345.46
705             $amount_value = sprintf "%.2f", $amount_value; # 123 => 123.00
706             $amount_value =~ s/\.//g; # 123.00 => 12300
707              
708             ########### get XML template for request ##########
709              
710             my $xml_template = _get_xml_template( $self->action );
711              
712             ########### initialize data for template ##########
713              
714             my $template_vars = {
715             merchantCode => $self->login,
716             version => $self->version,
717             orderCode => $content{order_number},
718             amount => {
719             value => $amount_value,
720             currencyCode => $self->currency,
721             exponent => $amount_exponent,
722             },
723             };
724              
725             return ($xml_template, $template_vars);
726             }
727              
728              
729             sub _launder_envariables
730             {
731             my ($installation_id, $merchant_code, $xml_password, $debug);
732              
733             if ( $ENV{WORLDPAY_INSTALLATION_ID} ) { # must be all digits
734             ( $installation_id ) = $ENV{WORLDPAY_INSTALLATION_ID} =~ m/^ ( \d+ ) $/x;
735             }
736              
737             if ( $ENV{WORLDPAY_MERCHANT_CODE} ) { # must be alphanumeric
738             ( $merchant_code ) = $ENV{WORLDPAY_MERCHANT_CODE} =~ m/^ ( \w+ ) $/x;
739             }
740              
741             if ( $ENV{WORLDPAY_XML_PASSWORD} ) { # must be alphanumeric
742             ( $xml_password ) = $ENV{WORLDPAY_XML_PASSWORD} =~ m/^ ( \w+ ) $/x;
743             }
744              
745             if ( $ENV{WORLDPAY_DEBUG} ) { # digits or pathname
746             ( $debug ) = $ENV{WORLDPAY_DEBUG} =~ m{^ ( \d+ | [-/.\w]+ ) $}x;
747             }
748              
749             @ENV{qw/
750             WORLDPAY_INSTALLATION_ID
751             WORLDPAY_MERCHANT_CODE
752             WORLDPAY_XML_PASSWORD
753             WORLDPAY_DEBUG
754             /} = (
755             $installation_id || '',
756             $merchant_code || '',
757             $xml_password || '',
758             $debug || '',
759             );
760              
761             return;
762             }
763              
764              
765             sub required_fields
766             {
767             my($self, @fields) = @_;
768              
769             my %content = $self->content();
770              
771             foreach (@fields) {
772             # croak("missing required field $_") unless exists $content{$_}; # standard B::OP check
773             croak("missing required field $_") unless $content{$_}; # modified for B::OP::WorldPay
774             }
775             }
776              
777              
778             sub _get_xml_template
779             {
780             my ($action, $payment_type) = @_;;
781              
782             my ($payment_xml, $elv_payment_xml, $inquiry_xml, $cancel_xml, $refund_xml, $cancel_or_refund_xml);
783              
784             $payment_xml = <<'EOS';
785            
786            
787             "-//WorldPay/DTD WorldPay PaymentService v1//EN"
788             "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd">
789              
790            
791            
792            
793             [% description %]
794            
795             [%- IF orderContent -%]
796            
797             [%- END %]
798            
799             <[% paymentDetails.paymentType %]>
800             [% paymentDetails.cardNumber %]
801            
802            
803            
804             [% paymentDetails.cardHolderName %]
805             [%- IF paymentDetails.startDate.month && paymentDetails.startDate.year %]
806            
807            
808            
809             [%- END -%]
810             [%- IF paymentDetails.issueNumber %]
811             [% paymentDetails.issueNumber %]
812             [%- END -%]
813             [%- IF paymentDetails.cvc %]
814             [% paymentDetails.cvc %]
815             [%- END -%]
816             [%- IF paymentDetails.cardAddress.firstName ||
817             paymentDetails.cardAddress.lastName ||
818             paymentDetails.cardAddress.street ||
819             paymentDetails.cardAddress.postalCode ||
820             paymentDetails.cardAddress.countryCode ||
821             paymentDetails.cardAddress.telephoneNumber
822             %]
823            
824            
825             [%- IF paymentDetails.cardAddress.firstName %]
826             [% paymentDetails.cardAddress.firstName %]
827             [%- END -%]
828             [%- IF paymentDetails.cardAddress.lastName %]
829             [% paymentDetails.cardAddress.lastName %]
830             [%- END -%]
831             [%- IF paymentDetails.cardAddress.street %]
832             [% paymentDetails.cardAddress.street %]
833             [%- END -%]
834             [%- IF paymentDetails.cardAddress.postalCode.defined %]
835             [% paymentDetails.cardAddress.postalCode %]
836             [%- END -%]
837             [%- IF paymentDetails.cardAddress.countryCode %]
838             [% paymentDetails.cardAddress.countryCode %]
839             [%- END -%]
840             [%- IF paymentDetails.cardAddress.telephoneNumber %]
841             [% paymentDetails.cardAddress.telephoneNumber %]
842             [%- END %]
843            
844            
845             [%- END %]
846            
847             [%- IF session.shopperIPAddress && session.id %]
848            
849             [%- END %]
850            
851             [%- IF shopper.acceptHeader && shopper.userAgentHeader %]
852            
853            
854             [% shopper.acceptHeader %]
855             [% shopper.userAgentHeader %]
856            
857            
858             [%- END %]
859            
860            
861            
862             EOS
863              
864             $elv_payment_xml = <<'EOS';
865            
866            
867             "-//WorldPay/DTD WorldPay PaymentService v1//EN"
868             "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd">
869              
870            
871            
872            
873             [% description %]
874            
875             [%- IF orderContent -%]
876            
877             [%- END %]
878            
879            
880             [% paymentDetails.accountHolderName %]
881             [% paymentDetails.bankAccountNr %]
882             [% paymentDetails.bankName %]
883             [% paymentDetails.bankLocation %]
884             [% paymentDetails.bankLocationId %]
885            
886            
887            
888            
889            
890             EOS
891              
892             $inquiry_xml = <<'EOS';
893            
894            
895             "-//WorldPay/DTD WorldPay PaymentService v1//EN"
896             "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd">
897              
898            
899            
900            
901            
902            
903             EOS
904              
905             $cancel_xml = <<'EOS';
906            
907            
908             "-//WorldPay/DTD WorldPay PaymentService v1//EN"
909             "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd">
910              
911            
912            
913            
914            
915            
916            
917            
918             EOS
919              
920             $refund_xml = <<'EOS';
921            
922            
923             "-//WorldPay/DTD WorldPay PaymentService v1//EN"
924             "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd">
925              
926            
927            
928            
929            
930            
931             value="[% amount.value %]"
932             currencyCode="[% amount.currencyCode %]"
933             exponent="[% amount.exponent %]"
934             debitCreditIndicator="credit"
935             />
936            
937            
938            
939            
940             EOS
941              
942             $cancel_or_refund_xml = <<'EOS';
943            
944            
945             "-//WorldPay/DTD WorldPay PaymentService v1//EN"
946             "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd">
947              
948            
949            
950            
951            
952            
953            
954            
955             EOS
956              
957             if ($action eq 'status') {
958             return $inquiry_xml;
959             }
960             elsif ($action eq 'cancel') {
961             return $cancel_xml;
962             }
963             elsif ($action eq 'refund') {
964             return $refund_xml;
965             }
966             elsif ($action eq 'cancel_or_refund') {
967             return $cancel_or_refund_xml;
968             }
969             elsif (defined $payment_type && $payment_type eq 'ELV-SSL') {
970             return $elv_payment_xml;
971             }
972             else {
973             return $payment_xml;
974             }
975             }
976              
977             1;
978              
979             =head1 NAME
980              
981             Business::OnlinePayment::WorldPay - RBS WorldPay interface for Business::OnlinePayment
982              
983             =head1 SYNOPSIS
984              
985             use Business::OnlinePayment;
986              
987             my $tx = Business::OnlinePayment->new("WorldPay");
988              
989             $tx->content(
990             installation => '12345',
991             login => 'testdrive',
992             password => 'xyzzy',
993              
994             type => 'Visa',
995             action => 'payment', # 'status', 'cancel', 'refund', 'cancel_or_refund'
996             description => '20 English Roses',
997             amount => '49.95',
998             currency => 'GBP',
999             order_number => 'A00100',
1000              
1001             name => 'Claire Voyance',
1002             address => '123 Disk Drive',
1003             city => 'Anywhere',
1004             state => 'DE',
1005             zipcode => '19808',
1006             country => 'US',
1007             phone => '201-555-1212',
1008              
1009             card_number => '4007000000027',
1010             exp_date => '09/10',
1011             start_date => '01/03',
1012             issue_number => '002',
1013             cvc => '377',
1014             );
1015              
1016             # ELV payment:
1017             $tx->content(
1018             installation => '12345',
1019             login => 'testdrive',
1020             password => 'xyzzy',
1021              
1022             type => 'ELV',
1023             action => 'payment',
1024             description => '20 English Roses',
1025             amount => '49.95',
1026             currency => 'GBP',
1027             order_number => 'A00100',
1028              
1029             ######################################
1030             account_holder_name => 'Claire Voyance',
1031             bank_account_number => '92441196',
1032             bank_name => 'Bundesbank',
1033             bank_location => 'Berline',
1034             bank_location_id => '20030000',
1035             ######################################
1036             );
1037              
1038             $tx->set_server('test'); # 'live' (default) or 'test'
1039              
1040             $tx->debug(1); # debugging to STDERR
1041             $tx->debug(1, "/tmp/debugging.outfile"); # debugging output to file
1042              
1043             $tx->test_transaction(1); # another way to set server to test
1044              
1045             $tx->submit();
1046              
1047             print "server_response = ", $tx->server_response, "\n";
1048              
1049             print "is_success = ", $tx->is_success, "\n";
1050              
1051             print "authorization = ", $tx->authorization, "\n";
1052             print "error_message = ", $tx->error_message, "\n\n";
1053              
1054             print "result_code = ", $tx->result_code, "\n";
1055              
1056             print "cvv_response = ", $tx->cvv_response, "\n";
1057             print "avs_response = ", $tx->avs_response, "\n";
1058             print "risk_score = ", $tx->risk_score, "\n";
1059              
1060             if ($tx->is_success) {
1061             print "Card processed successfully: " . $tx->authorization . "\n";
1062             }
1063             else {
1064             print "Card was rejected: " . $tx->error_message . "\n";
1065             }
1066              
1067             =cut
1068              
1069             =head1 DESCRIPTION
1070              
1071             This module subclasses Business::OnlinePayment to provide a basic merchant
1072             processing interface for submitting transactions as XML requests in the
1073             direct model provided by RBS WorldPay.
1074              
1075             L
1076              
1077             L
1078              
1079             It currently implements payments, cancellations, refunds, and payment status inquiries.
1080              
1081             Orders submitted to the RBS WorldPay system are required to be valid XML
1082             files as specified in their Document Type Definition (DTD):
1083              
1084             L
1085              
1086             =head1 BUGS
1087              
1088             Please report any bugs or feature requests to
1089             C, or through the web interface at
1090             L.
1091             I will be notified, and then you'll automatically be notified of progress on your
1092             bug as I make changes.
1093              
1094             =head1 SUPPORT
1095              
1096             You can find documentation for this module with the perldoc command.
1097              
1098             perldoc Business::OnlinePayment::WorldPay
1099              
1100             You can also look for information at:
1101              
1102             =over 4
1103              
1104             =item * RT: CPAN's request tracker
1105              
1106             L
1107              
1108             =item * AnnoCPAN: Annotated CPAN documentation
1109              
1110             L
1111              
1112             =item * CPAN Ratings
1113              
1114             L
1115              
1116             =item * Search CPAN
1117              
1118             L
1119              
1120             =back
1121              
1122             =head1 SEE ALSO
1123              
1124             L
1125              
1126             =head1 AUTHOR
1127              
1128             Paul Grassie, Epaul.grassie@ardishealth.comE
1129              
1130             =head1 COPYRIGHT AND LICENSE
1131              
1132             Copyright (C) 2010 Paul Grassie, Ardis Health, http://www.ardishealth.com. All Rights Reserved.
1133              
1134             This library is free software; you can redistribute it and/or modify
1135             it under the same terms as Perl itself, either Perl version 5.8.8 or,
1136             at your option, any later version of Perl 5 you may have available.