line
stmt
bran
cond
sub
pod
time
code
1
package Business::OnlinePayment::IPayment;
2
3
1
1
22787
use 5.010001;
1
5
4
1
1
6
use strict;
1
2
1
23
5
1
1
5
use warnings;
1
6
1
30
6
7
# preparation
8
1
1
980
use XML::Compile::WSDL11;
0
0
9
use XML::Compile::SOAP11;
10
use XML::Compile::Transport::SOAPHTTP;
11
use Business::OnlinePayment::IPayment::Response;
12
use Business::OnlinePayment::IPayment::Transaction;
13
use Business::OnlinePayment::IPayment::Return;
14
use Digest::MD5 qw/md5_hex/;
15
use URI;
16
17
use Business::OnlinePayment 3;
18
use Moo;
19
20
use base 'Business::OnlinePayment';
21
22
# use Log::Report mode => 'DEBUG';
23
24
=head1 NAME
25
26
Business::OnlinePayment::IPayment - Checkout via Ipayment Silent Mode
27
28
=head1 VERSION
29
30
Version 0.08
31
32
=cut
33
34
our $VERSION = '0.08';
35
36
=head1 DESCRIPTION
37
38
This module provides an interface for online payments via gateway, using the
39
IPayment silent mode (L).
40
41
It supports payments, capture and reverse operations, and vault-related
42
functions.
43
44
=head1 SYNOPSIS
45
46
use Business::OnlinePayment::IPayment;
47
my %account = (
48
accountid => 99999,
49
trxuserid => 99998,
50
trxpassword => 0,
51
adminactionpassword => '5cfgRT34xsdedtFLdfHxj7tfwx24fe',
52
app_security_key => 'testtest',
53
wsdl_file => $wsdl_file,
54
success_url => 'http://example.net/checkout-payment',
55
failure_url => 'http://example.net/checkout-success',
56
hidden_trigger_rul => 'http://example.net/trigger',
57
);
58
59
60
my $secbopi = Business::OnlinePayment::IPayment->new(%account);
61
$secbopi->transaction(transactionType => 'preauth',
62
trxAmount => 5000);
63
# see Business::OnlinePayment::IPayment::Transaction for available options
64
65
$response = $ua->post('https://ipayment.de/merchant/99999/processor/2.0/',
66
{ ipayment_session_id => $secbopi->session_id,
67
addr_name => "Mario Pegula",
68
silent => 1,
69
cc_number => "4111111111111111",
70
cc_checkcode => "",
71
cc_expdate_month => "02",
72
trx_securityhash => $secbopi->trx_securityhash,
73
cc_expdate_year => "2014" });
74
75
76
=head2 ACCESSORS
77
78
=head3 Fixed values (accountData and processorUrls)
79
80
The following attributes should and can be set only in the
81
constructor, as they are pretty much fixed values.
82
83
=over 4
84
85
=item wsdl_file
86
87
The name of th WSDL file. It should be a local file.
88
89
=cut
90
91
has wsdl_file => (is => 'rw');
92
93
=item accountid
94
95
The Ipayment account id (the one put into the CGI url). Integer.
96
97
=cut
98
99
has accountid => (is => 'rw',
100
isa => sub {
101
die "Not an integer" unless $_[0] =~ m/^[0-9]+$/s
102
});
103
104
=item trxuserid
105
106
The application ID, you can in your ipayment configuration menu read
107
using Anwendung > Details. Integer
108
109
=cut
110
111
has trxuserid => (is => 'rw',
112
isa => sub {
113
die "Not an integer" unless $_[0] =~ m/^[0-9]+$/s
114
});
115
116
=item trxpassword
117
118
For each application, there is an application password which
119
automatically ipayment System is presented. The password consists of
120
numbers. You will find the application password in your ipayment
121
Anwendungen > Details
122
123
B
124
125
=cut
126
127
has trxpassword => (is => 'rw');
128
129
=item adminactionpassword
130
131
The admin password.
132
133
B
134
135
=cut
136
137
has adminactionpassword => (is => 'rw');
138
139
140
=item app_security_key
141
142
If this attribute is set, we will (and shall) send a checksum for the
143
parameters.
144
145
B
146
147
=cut
148
149
has app_security_key => (is => 'rw');
150
151
152
=item accountData
153
154
Accessor to retrieve the hash with the account data details. The
155
output will look like this:
156
157
accountData => {
158
accountid => 99999,
159
trxuserid => 99999,
160
trxpassword =>0,
161
adminactionpassword => '5cfgRT34xsdedtFLdfHxj7tfwx24fe'}
162
163
164
=cut
165
166
sub accountData {
167
my $self = shift;
168
my %account_data = ( # mandatory
169
accountId => $self->accountid,
170
trxuserId => $self->trxuserid,
171
trxpassword => $self->trxpassword
172
);
173
my $adminpass = $self->adminactionpassword;
174
if (defined $adminpass) {
175
$account_data{adminactionpassword} = $adminpass;
176
}
177
return \%account_data;
178
}
179
180
=item success_url
181
182
Mandatory (for us) field, where to redirect the user in case of success.
183
184
CGI-Name: C
185
186
I
187
script.> (no need to C)
188
189
=cut
190
191
has success_url => (is => 'rw',
192
isa => sub { die "Missing success url" unless $_[0] },
193
default => sub { die "Missing success url" },
194
);
195
196
=item failure_url
197
198
Mandatory (for us) field, where to redirect the user in case of failure.
199
200
CGI Name: C
201
Data type: String
202
203
This URL is more in case of failure of ipayment system with the error information and parameters B. This URL must point to a CGI script that can handle the paramaters.
204
205
=cut
206
207
has failure_url => (is => 'rw',
208
isa => sub { die "Missing failure url" unless $_[0] },
209
default => sub { die "Missing success url" },
210
);
211
212
213
=item hidden_trigger_url
214
215
Optional url for the hidden trigger.
216
217
=cut
218
219
has hidden_trigger_url => (is => 'rw');
220
221
222
=item processorUrls
223
224
Return the hashref with the defined urls
225
226
=back
227
228
=cut
229
230
sub processorUrls {
231
my $self = shift;
232
return {
233
redirectUrl => $self->success_url,
234
silentErrorUrl => $self->failure_url,
235
hiddenTriggerUrl => $self->hidden_trigger_url
236
};
237
}
238
239
240
241
242
243
=head3 error
244
245
This accessors point to a XML::Compile::SOAP backtrace. The object is
246
quite large and deeply nested, but it's there just in case we need it.
247
248
=cut
249
250
has error => (is => 'rwp');
251
252
=head3 debug
253
254
Every call to session id stores the trace into this attribute.
255
256
=cut
257
258
has debug => (is => 'rwp');
259
260
=head3 trx_obj
261
262
Attribute to hold a L object
263
264
=cut
265
266
has trx_obj => (is => 'rwp');
267
268
=head3 transaction
269
270
Constructor for the object above. All the argument are passed verbatim
271
to the L constructor,
272
then the object is stored.
273
274
=cut
275
276
sub transaction {
277
my $self = shift;
278
my %trx = @_;
279
my $trxdata = Business::OnlinePayment::IPayment::Transaction->new(%trx);
280
$self->_set_trx_obj($trxdata);
281
}
282
283
284
=head2 METHODS
285
286
=over 4
287
288
=item session_id
289
290
This is the main method to call. The session is not stored in the object, because it can used only B. So calling session_id will send the data to the SOAP service and retrieve the session key.
291
292
=cut
293
294
sub session_id {
295
my $self = shift;
296
# clean eventually stale data
297
$self->_set_error(undef);
298
299
my %args = (
300
# fixed values
301
accountData => $self->accountData,
302
processorUrls => $self->processorUrls,
303
# then the transaction
304
transactionType => $self->trx_obj->transactionType,
305
paymentType => $self->trx_obj->paymentType,
306
transactionData => $self->trx_obj->transactionData,
307
);
308
# and the options, if needed
309
if ($self->trx_obj->options) {
310
$args{options} = $self->trx_obj->options;
311
}
312
313
# do the request passing the accountData
314
my ($res, $trace) = $self->_get_soap_object('createSession')->(%args);
315
$self->_set_debug($trace);
316
317
# check if we got something valuable
318
unless ($res and
319
ref($res) eq 'HASH' and
320
exists $res->{createSessionResponse}->{sessionId}) {
321
# ok, we got an error. Save the trace to the error and return
322
$self->_set_error($trace);
323
return undef;
324
}
325
326
return $res->{createSessionResponse}->{sessionId};
327
# please note that we don't store the sessionId. It's a fire and forget.
328
}
329
330
331
=item raw_response_hash
332
333
Debug for the arguments passed to IPayment::Return;
334
335
=cut
336
337
has raw_response_hash => (is => 'rwp');
338
339
340
341
=item capture($ret_trx_number, $amount, $currency, $opts)
342
343
Charge an amount previously preauth'ed. C<$amount> and C<$currency>
344
are optional and may be used to charge partial amounts. C<$amount> and
345
C<$currency> follow the same rules of C and C
346
of L (no decimal,
347
usually multiply by 100).
348
349
The last optional argument should be a hashref with additional
350
parameters to pass to transactionData (notably shopperId).
351
352
=cut
353
354
sub _do_post_payment_op {
355
my ($self, $op, $number, $amount, $currency, $trxdetails) = @_;
356
unless (defined $number) {
357
$self->_set_error("Missing transaction number");
358
return undef;
359
}
360
die "Wrong usage, missing operation" unless $op;
361
my %args = (
362
accountData => $self->accountData,
363
origTrxNumber => $number,
364
);
365
# amount is always mandatory for transactionData
366
if ($amount) {
367
my %trxdata;
368
die "Wrong amount $amount!\n" unless ($amount =~ m/^[1-9][0-9]*$/s);
369
370
unless ($currency) {
371
$currency = 'EUR'
372
}
373
unless ($currency =~ m/^[A-Z]{3}$/s) {
374
die "Wrong currency name!\n";
375
}
376
377
%trxdata = (
378
trxAmount => $amount,
379
trxCurrency => $currency,
380
);
381
if ($trxdetails and
382
(ref($trxdetails) eq 'HASH')
383
and %$trxdetails) {
384
foreach my $k (keys %$trxdetails) {
385
unless ($trxdata{$k}) {
386
$trxdata{$k} = $trxdetails->{$k}
387
}
388
}
389
}
390
$args{transactionData} = \%trxdata;
391
}
392
393
die "Wrong operation" unless ($op eq 'capture' or
394
$op eq 'refund' or
395
$op eq 'reverse');
396
397
my ($res, $trace) = $self->_get_soap_object($op)->(%args);
398
$self->_set_debug($trace);
399
$self->_set_raw_response_hash($res);
400
if ($res and ref($res) eq 'HASH' and
401
exists $res->{"${op}Response"}->{ipaymentReturn}) {
402
return Business::OnlinePayment::IPayment::Return
403
->new($res->{"${op}Response"}->{ipaymentReturn});
404
}
405
else {
406
$self->_set_error($trace);
407
return undef;
408
}
409
}
410
411
=item datastorage_op($datastorage_id)
412
413
After calling C, if you have a valid datastorage id, you
414
may want to use that instead of creating a session and use the form.
415
416
This method will do a SOAP request to the Ipayment server, using the
417
transaction details provided in the call to C, and do the
418
requested operation. So far it's supported preauth and auth. The
419
capture and other operations should be done via its own method (which
420
don't require the datastorage, but simply the previous transaction's
421
id).
422
423
=cut
424
425
sub datastorage_op {
426
my ($self, $id) = @_;
427
return unless $id;
428
429
$self->_set_error(undef);
430
# this should be fully populated by now
431
my %args = (
432
accountData => $self->accountData,
433
paymentData => {
434
storageData => {
435
fromDatastorageId => $id,
436
},
437
},
438
transactionData => $self->trx_obj->transactionData,
439
);
440
my $operation = $self->trx_obj->transactionType;
441
# append the options if needed
442
if ($self->trx_obj->options) {
443
$args{options} = $self->trx_obj->options;
444
}
445
my ($res, $trace) = $self->_get_soap_object($operation)->(%args);
446
$self->_set_debug($trace);
447
$self->_set_raw_response_hash($res);
448
449
# in the trasaction object the call is defined as in CGI, but we
450
# need the SOAP one
451
my $op = $self->_translate_to_soap_call($operation);
452
453
if ($res and ref($res) eq 'HASH' and
454
exists $res->{"${op}Response"}->{ipaymentReturn}) {
455
return Business::OnlinePayment::IPayment::Return
456
->new($res->{"${op}Response"}->{ipaymentReturn});
457
}
458
else {
459
$self->_set_error($trace);
460
return undef;
461
}
462
}
463
464
=item expire_datastorage($id)
465
466
Given the storage id passed as argument, expire it. Keep in mind that
467
expiring it multiple times returns always true, so the return code is
468
not really interesting.
469
470
It returns 0 if the storage didn't exist.
471
472
=cut
473
474
sub expire_datastorage {
475
my ($self, $id) = @_;
476
return unless $id;
477
my $op = 'expireDatastorage';
478
my %args = (
479
accountData => $self->accountData,
480
datastorageId => $id,
481
);
482
my ($res, $trace) = $self->_get_soap_object($op)->(%args);
483
$self->_set_debug($trace);
484
$self->_set_raw_response_hash($res);
485
if ($res and ref($res) eq 'HASH' and
486
exists $res->{"${op}Response"}->{expireDatastorageReturn}) {
487
return $res->{"${op}Response"}->{expireDatastorageReturn};
488
}
489
return;
490
}
491
492
493
sub capture {
494
my ($self, $number, $amount, $currency, $opts) = @_;
495
# init the soap, if not already
496
return $self->_do_post_payment_op(capture => $number,
497
$amount, $currency, $opts);
498
}
499
500
=item reverse($ret_trx_number)
501
502
Release the amount previously preauth'ed, passing the original
503
transaction number. No partial amount can be released, and will
504
succeed only if no charging has been done.
505
506
=cut
507
508
509
sub reverse {
510
my ($self, $number) = @_;
511
# we don't pass $amount and $currency
512
return $self->_do_post_payment_op(reverse => $number);
513
}
514
515
=item refund($ret_trx_number, $amount, $currency, $opts)
516
517
Refund the given amount. Please note that we have to pass the
518
transaction number B, not the C one.
519
520
The last optional argument should be a hashref with additional
521
parameters to pass to transactionData (notably shopperId).
522
523
=cut
524
525
sub refund {
526
my ($self, $number, $amount, $currency, $opts) = @_;
527
return $self->_do_post_payment_op(refund => $number,
528
$amount, $currency, $opts);
529
}
530
531
# accessors to soap objects
532
533
has _soap_createSession => (is => 'rw');
534
has _soap_capture => (is => 'rw');
535
has _soap_reverse => (is => 'rw');
536
has _soap_refund => (is => 'rw');
537
has _soap_preAuthorize => (is => 'rw');
538
has _soap_authorize => (is => 'rw');
539
has _soap_expireDatastorage => (is => 'rw');
540
541
sub _get_soap_object {
542
my ($self, $op) = @_;
543
my $call = $self->_translate_to_soap_call($op);
544
my $accessor = "_soap_" . $call;
545
my $obj = $self->$accessor;
546
return $obj if $obj;
547
my $wsdl = XML::Compile::WSDL11->new($self->wsdl_file);
548
my $client = $wsdl->compileClient($call);
549
# set the object
550
$self->$accessor($client);
551
return $self->$accessor;
552
}
553
554
# this method may be used for to do a sanity check, as it will die on
555
# undef/wrong values.
556
557
sub _translate_to_soap_call {
558
my ($self, $op) = @_;
559
die "No operation provided!" unless $op;
560
my %hash = (capture => 'capture',
561
reverse => 'reverse',
562
refund => 'refund',
563
preauth => 'preAuthorize',
564
auth => 'authorize',
565
authorize => 'authorize',
566
preAuthorize => 'preAuthorize',
567
createSession => 'createSession',
568
expireDatastorage => 'expireDatastorage',
569
);
570
die "Wrong call $op!" unless $hash{$op};
571
return $hash{$op};
572
}
573
574
=back
575
576
=head2 SOAP specification
577
578
Name: createSession
579
Binding: ipaymentBinding
580
Endpoint: https://ipayment.de/service/3.0/
581
SoapAction: createSession
582
Input:
583
use: literal
584
namespace: https://ipayment.de/service_v3/binding
585
message: createSessionRequest
586
parts:
587
accountData: https://ipayment.de/service_v3/extern:AccountData
588
transactionData: https://ipayment.de/service_v3/extern:TransactionData
589
transactionType: https://ipayment.de/service_v3/extern:TransactionType
590
paymentType: https://ipayment.de/service_v3/extern:PaymentType
591
options: https://ipayment.de/service_v3/extern:OptionData
592
processorUrls: https://ipayment.de/service_v3/extern:ProcessorUrlData
593
Output:
594
use: literal
595
namespace: https://ipayment.de/service_v3/binding
596
message: createSessionResponse
597
parts:
598
sessionId: http://www.w3.org/2001/XMLSchema:string
599
Style: rpc
600
Transport: http://schemas.xmlsoap.org/soap/http
601
602
603
=head2 SECURITY
604
605
=over 4
606
607
=item trx_securityhash
608
609
If we have a security key, we trigger the hash generation, so we can
610
double check the result.
611
612
CGI Name: C
613
Data type: string, maximum 32 characters
614
615
Security hash of CGI command concatenating Id, amount, currency,
616
password, Transaction Security Key (should be set in the configuration
617
menu using ipayment). The hash is C, C,
618
C, C and the I.
619
620
md5_hex($trxuser_id . $trx_amount . $trx_currency . $trxpassword . $sec_key);
621
622
perl -e 'use Digest::MD5 qw/md5_hex/;
623
print md5_hex("99998" . 5000 . "EUR" . 0 . "testtest"), "\n";'
624
# => then in the form
625
626
value="db4812171baef817dec0cd56c0f5c8cd">
627
628
=cut
629
630
sub trx_securityhash {
631
my $self = shift;
632
unless ($self->app_security_key) {
633
warn "hash requested, but app_security_key wasn't provided!\n";
634
return;
635
}
636
return md5_hex($self->trxuserid .
637
$self->trx_obj->trxAmount .
638
$self->trx_obj->trxCurrency .
639
$self->trxpassword .
640
$self->app_security_key);
641
}
642
643
=back
644
645
=head2 UTILITIES
646
647
=head3 get_response_obj($rawuri) or get_response_obj(%params)
648
649
To be sure the transaction happened as aspected, we have to check this back.
650
Expected hash:
651
652
Success:
653
654
'ret_transtime' => '08:42:05', 'ret_transtime' => '08:42:03',
655
'ret_errorcode' => '0', 'ret_errorcode' => '0',
656
'redirect_needed' => '0', 'redirect_needed' => '0',
657
'ret_transdate' => '14.03.13', 'ret_transdate' => '14.03.13',
658
'addr_name' => 'Mario Pegula', 'addr_name' => 'Mario Rossi',
659
'trx_paymentmethod' => 'VisaCard', 'trx_paymentmethod' => 'AmexCard',
660
'ret_authcode' => '', 'ret_authcode' => '',
661
'trx_currency' => 'EUR', 'trx_currency' => 'EUR',
662
'ret_url_checksum' => 'md5sum',
663
'ret_param_checksum' => 'md5sum',
664
'ret_ip' => '88.198.37.147', 'ret_ip' => '88.198.37.147',
665
'trx_typ' => 'preauth', 'trx_typ' => 'preauth',
666
'ret_trx_number' => '1-83443831', 'ret_trx_number' => '1-83443830',
667
'ret_status' => 'SUCCESS', 'ret_status' => 'SUCCESS',
668
'trx_paymenttyp' => 'cc', 'trx_paymenttyp' => 'cc',
669
'trx_paymentdata_country' => 'US',
670
'trx_amount' => '5000', 'trx_amount' => '1000',
671
'ret_booknr' => '1-83443831', 'ret_booknr' => '1-83443830',
672
'trxuser_id' => '99998', 'trxuser_id' => '99999',
673
'trx_remoteip_country' => 'DE' 'trx_remoteip_country' => 'DE'
674
675
Returns a L object, so you
676
can call ->is_success on it.
677
678
This is just a shortcut for
679
680
Business::OnlinePayment::IPayment::Response->new(%params);
681
682
with C and C inherited from the fixed
683
values of this class.
684
685
=cut
686
687
sub get_response_obj {
688
my ($self, @args) = @_;
689
my %details;
690
# only one argument: we have an URI
691
if (@args == 1) {
692
my $raw_url = shift(@args);
693
my $uri = URI->new($raw_url);
694
%details = $uri->query_form;
695
$details{raw_url} = $raw_url;
696
}
697
elsif ((@args % 2) == 0) {
698
%details = @args;
699
}
700
else {
701
die "Arguments to validate the response not provided "
702
. "(paramaters or raw url";
703
}
704
unless (exists $details{my_security_key}) {
705
$details{my_security_key} = $self->app_security_key;
706
}
707
unless (exists $details{my_userid}) {
708
$details{my_userid} = $self->trxuserid;
709
}
710
return Business::OnlinePayment::IPayment::Response->new(%details);
711
}
712
713
=head3 ipayment_cgi_location
714
715
Returns the correct url where the customer posts the CC data, which is simply:
716
L/processor/2.0/>
717
718
=cut
719
720
sub ipayment_cgi_location {
721
my $self = shift;
722
return 'https://ipayment.de/merchant/' . $self->accountid
723
. '/processor/2.0/';
724
}
725
726
727
=head2 Additional information
728
729
=head3 country
730
731
Country code of the cardholder of the current
732
L object
733
734
Being these information transaction specific, if a transaction has not
735
been initiated, the method will not do anything nor will return
736
anything.
737
738
UK will be translated to GB, and EI to IE.
739
740
741
=cut
742
743
sub country {
744
my $self = shift;
745
#
746
return unless $self->trx_obj;
747
if (@_ == 1) {
748
$self->trx_obj->addr_info->{country} = shift;
749
}
750
my $country = uc($self->trx_obj->addr_info->{country});
751
return unless $country =~ m/^[A-Z]{2,3}$/s;
752
if ($country eq 'UK') {
753
return 'GB';
754
}
755
elsif ($country eq 'EI') {
756
return 'IE';
757
}
758
else {
759
return $country;
760
}
761
}
762
763
=head1 TESTING
764
765
Test credit card numbers can be found here: L.
766
767
=head1 AUTHOR
768
769
Marco Pessotto, C<< >>
770
771
=head1 BUGS
772
773
Please report any bugs or feature requests to C, or through
774
the web interface at L. I will be notified, and then you'll
775
automatically be notified of progress on your bug as I make changes.
776
777
778
779
780
=head1 SUPPORT
781
782
You can find documentation for this module with the perldoc command.
783
784
perldoc Business::OnlinePayment::IPayment
785
786
787
You can also look for information at:
788
789
=over 4
790
791
=item * RT: CPAN's request tracker (report bugs here)
792
793
L
794
795
=item * AnnoCPAN: Annotated CPAN documentation
796
797
L
798
799
=item * CPAN Ratings
800
801
L
802
803
=item * Search CPAN
804
805
L
806
807
=back
808
809
810
=head1 ACKNOWLEDGEMENTS
811
812
Thanks to Stefan Hornburg (Racke) C for the initial
813
code, ideas and support.
814
815
=head1 LICENSE AND COPYRIGHT
816
817
Copyright 2013-2014 Marco Pessotto.
818
819
This program is free software; you can redistribute it and/or modify it
820
under the terms of the the Artistic License (2.0). You may obtain a
821
copy of the full license at:
822
823
L
824
825
Any use, modification, and distribution of the Standard or Modified
826
Versions is governed by this Artistic License. By using, modifying or
827
distributing the Package, you accept this license. Do not use, modify,
828
or distribute the Package, if you do not accept this license.
829
830
If your Modified Version has been derived from a Modified Version made
831
by someone other than you, you are nevertheless required to ensure that
832
your Modified Version complies with the requirements of this license.
833
834
This license does not grant you the right to use any trademark, service
835
mark, tradename, or logo of the Copyright Holder.
836
837
This license includes the non-exclusive, worldwide, free-of-charge
838
patent license to make, have made, use, offer to sell, sell, import and
839
otherwise transfer the Package with respect to any patent claims
840
licensable by the Copyright Holder that are necessarily infringed by the
841
Package. If you institute patent litigation (including a cross-claim or
842
counterclaim) against any party alleging that the Package constitutes
843
direct or contributory patent infringement, then this Artistic License
844
to you shall terminate on the date that such litigation is filed.
845
846
Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
847
AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
848
THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
849
PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
850
YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
851
CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
852
CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
853
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
854
855
856
=cut
857
858
1; # End of Business::OnlinePayment::IPayment