71513: Card tokenization [American Express tests]
[freeside.git] / FS / t / suite / 14-tokenization_refund.t
1 #!/usr/bin/perl
2
3 use strict;
4 use FS::Test;
5 use Test::More;
6 use FS::Conf;
7 use FS::cust_main;
8 use Business::CreditCard qw(generate_last_digit);
9 use DateTime;
10 if ( stat('/usr/local/etc/freeside/cardfortresstest.txt') ) {
11   plan tests => 66;
12 } else {
13   plan skip_all => 'CardFortress test encryption key is not installed.';
14 }
15
16 #local $FS::cust_main::Billing_Realtime::DEBUG = 2;
17
18 my $fs = FS::Test->new( user => 'admin' );
19 my $conf = FS::Conf->new;
20 my $err;
21 my @bopconf;
22
23 ### can only run on test database (company name "Freeside Test")
24 like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT('');
25
26 # these will just get in the way for now
27 foreach my $apg ($fs->qsearch('agent_payment_gateway')) {
28   $err = $apg->delete;
29   last if $err;
30 }
31 ok( !$err, 'removing agent gateway overrides' ) or BAIL_OUT($err);
32
33 # will need this
34 my $reason = FS::reason->new_or_existing(
35   reason => 'Token Test',
36   type   => 'Refund',
37   class  => 'F',
38 );
39 isa_ok ( $reason, 'FS::reason', "refund reason" ) or BAIL_OUT('');
40
41 # non-tokenizing gateway
42 push @bopconf,
43 'IPPay
44 TESTTERMINAL';
45
46 # tokenizing gateway
47 push @bopconf,
48 'CardFortress
49 cardfortresstest
50 (TEST54)
51 Normal Authorization
52 gateway
53 IPPay
54 gateway_login
55 TESTTERMINAL
56 gateway_password
57
58 private_key
59 /usr/local/etc/freeside/cardfortresstest.txt';
60
61 foreach my $voiding (0,1) {
62   my $noun = $voiding ? 'void' : 'refund';
63
64   if ($voiding) {
65     $conf->delete('disable_void_after');
66     ok( !$conf->exists('disable_void_after'), 'set disable_void_after to produce voids' ) or BAIL_OUT('');
67   } else {
68     $conf->set('disable_void_after' => '0');
69     is( $conf->config('disable_void_after'), '0', 'set disable_void_after to produce refunds' ) or BAIL_OUT('');
70   }
71
72   # for attempting refund post-tokenization
73   my $n_cust_main;
74   my $n_cust_pay;
75
76   foreach my $tokenizing (0,1) {
77     my $adj = $tokenizing ? 'tokenizable' : 'non-tokenizable';
78
79     # set payment gateway
80     $conf->set('business-onlinepayment' => $bopconf[$tokenizing]);
81     is( join("\n",$conf->config('business-onlinepayment')), $bopconf[$tokenizing], "set $adj $noun default gateway" ) or BAIL_OUT('');
82
83     # make sure we're upgraded, only need to do it once,
84     # use non-tokenizing gateway for speed,
85     # but doesn't matter if existing records are tokenized or not,
86     # this suite is all about testing new record creation
87     if (!$tokenizing && !$voiding) {
88       $err = system('freeside-upgrade','-q','admin');
89       ok( !$err, 'upgrade freeside' ) or BAIL_OUT('Error string: '.$!);
90     }
91
92     if ($tokenizing) {
93
94       my $n_paynum = $n_cust_pay->paynum;
95
96       # refund the previous non-tokenized payment through CF
97       $err = $n_cust_main->realtime_refund_bop({
98         reasonnum => $reason->reasonnum,
99         paynum    => $n_paynum,
100         method    => 'CC',
101       });
102       ok( !$err, "run post-switch $noun" ) or BAIL_OUT($err);
103
104       my $n_cust_pay_void = $fs->qsearchs('cust_pay_void',{ paynum => $n_paynum });
105       my $n_cust_refund   = $fs->qsearchs('cust_refund',{ source_paynum => $n_paynum });
106
107       if ($voiding) {
108
109         # check for void record
110         isa_ok( $n_cust_pay_void, 'FS::cust_pay_void', 'post-switch void') or BAIL_OUT("paynum $n_paynum");
111
112         # check that void tokenized
113         ok ( $n_cust_pay_void->tokenized, "post-switch void tokenized" ) or BAIL_OUT("paynum $n_paynum");
114
115         # check for no refund record
116         ok( !$n_cust_refund, "post-switch void did not generate cust_refund" ) or BAIL_OUT("paynum $n_paynum");
117
118       } else {
119
120         # check for refund record
121         isa_ok( $n_cust_refund, 'FS::cust_refund', 'post-switch refund') or BAIL_OUT("paynum $n_paynum");
122
123         # check that refund tokenized
124         ok ( $n_cust_refund->tokenized, "post-switch refund tokenized" ) or BAIL_OUT("paynum $n_paynum");
125
126         # check for no refund record
127         ok( !$n_cust_pay_void, "post-switch refund did not generate cust_pay_void" ) or BAIL_OUT("paynum $n_paynum");
128
129       }
130
131     }
132
133     # create customer
134     my $cust_main = $fs->new_customer($adj.'X'.$noun);
135     isa_ok ( $cust_main, 'FS::cust_main', "$adj $noun customer" ) or BAIL_OUT('');
136
137     # insert customer
138     $err = $cust_main->insert;
139     ok( !$err, "insert $adj $noun customer" ) or BAIL_OUT($err);
140
141     # add card
142     my $cust_payby;
143     my %card = random_card();
144     $err = $cust_main->save_cust_payby(
145       %card,
146       payment_payby => $card{'payby'},
147       auto => 1,
148       saved_cust_payby => \$cust_payby
149     );
150     ok( !$err, "save $adj $noun card" ) or BAIL_OUT($err);
151
152     # retrieve card
153     isa_ok ( $cust_payby, 'FS::cust_payby', "$adj $noun card" ) or BAIL_OUT('');
154
155     # check that card tokenized or not
156     if ($tokenizing) {
157       ok( $cust_payby->tokenized, "new $noun cust card tokenized" ) or BAIL_OUT('');
158     } else {
159       ok( !$cust_payby->tokenized, "new $noun cust card not tokenized" ) or BAIL_OUT('');
160     }
161
162     # run a payment
163     $err = $cust_main->realtime_cust_payby( amount => '1.00' );
164     ok( !$err, "run $adj $noun payment" ) or BAIL_OUT($err);
165
166     # get the payment
167     my $cust_pay = $fs->qsearchs('cust_pay',{ custnum => $cust_main->custnum }); 
168     isa_ok ( $cust_pay, 'FS::cust_pay', "$adj $noun payment" ) or BAIL_OUT('');
169
170     # refund the payment
171     $err = $cust_main->realtime_refund_bop({
172       reasonnum => $reason->reasonnum,
173       paynum    => $cust_pay->paynum,
174       method    => 'CC',
175     });
176     ok( !$err, "run $adj $noun" ) or BAIL_OUT($err);
177
178     unless ($tokenizing) {
179
180       # run a second payment, to refund after switch
181       $err = $cust_main->realtime_cust_payby( amount => '2.00' );
182       ok( !$err, "run $adj $noun second payment" ) or BAIL_OUT($err);
183     
184       # get the second payment
185       $n_cust_pay = $fs->qsearchs('cust_pay',{ custnum => $cust_main->custnum, paid => '2.00' });
186       isa_ok ( $n_cust_pay, 'FS::cust_pay', "$adj $noun second payment" ) or BAIL_OUT('');
187
188       $n_cust_main = $cust_main;
189
190     }
191
192     #check that all transactions tokenized or not
193     foreach my $table (qw(cust_pay_pending cust_pay cust_pay_void cust_refund)) {
194       foreach my $record ($fs->qsearch($table,{ custnum => $cust_main->custnum })) {
195         if ($tokenizing) {
196           $err = "record not tokenized: $table ".$record->get($record->primary_key)
197             unless $record->tokenized;
198         } else {
199           $err = "record tokenized: $table ".$record->get($record->primary_key)
200             if $record->tokenized;
201         }
202         last if $err;
203       }
204     }
205     ok( !$err, "$adj transaction token check" ) or BAIL_OUT($err);
206
207     if ($voiding) {
208
209       #make sure we voided
210       ok( $fs->qsearch('cust_pay_void',{ custnum => $cust_main->custnum}), "$adj $noun record found" ) or BAIL_OUT('');
211
212       #make sure we didn't generate refund records
213       ok( !$fs->qsearch('cust_refund',{ custnum => $cust_main->custnum}), "$adj $noun did not generate cust_refund" ) or BAIL_OUT('');
214
215     } else {
216
217       #make sure we refunded
218       ok( $fs->qsearch('cust_refund',{ custnum => $cust_main->custnum}), "$adj $noun record found" ) or BAIL_OUT('');
219
220       #make sure we didn't generate void records
221       ok( !$fs->qsearch('cust_pay_void',{ custnum => $cust_main->custnum}), "$adj $noun did not generate cust_pay_void" ) or BAIL_OUT('');
222
223     }
224
225   } #end of tokenizing or not
226
227 } # end of voiding or not
228
229 exit;
230
231 sub random_card {
232 #  my $payinfo = '4111' . join('', map { int(rand(10)) } 1 .. 11);
233 #  $payinfo .= generate_last_digit($payinfo);
234 # Use AmEx for everything, to make sure cardtype gets set correctly
235   my $payinfo = '347594362484937'; #American Express
236   my $paydate = DateTime->now
237                 ->add('years' => 1)
238                 ->truncate(to => 'month')
239                 ->strftime('%F');
240   return ( 'payby'    => 'CARD',
241            'payinfo'  => $payinfo,
242            'paydate'  => $paydate,
243            'payname'  => 'Tokenize Me',
244            'paycvv'   => '1234', #American Express
245   );
246 }
247
248 1;
249