71513: Card tokenization [v4 test db handling]
[freeside.git] / FS / t / suite / 13-tokenization.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 => 20;
12 } else {
13   plan skip_all => 'CardFortress test encryption key is not installed.';
14 }
15
16 ### can only run on test database (company name "Freeside Test")
17 ### will run upgrade, which uses lots of prints & warns beyond regular test output
18
19 my $fs = FS::Test->new( user => 'admin' );
20 my $conf = FS::Conf->new;
21 my $err;
22 my $bopconf;
23
24 like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT('');
25
26 # some pre-upgrade cleanup, upgrade will fail if these are still configured
27 foreach my $cust_main ( $fs->qsearch('cust_main') ) {
28   my @count = $fs->qsearch('agent_payment_gateway', { agentnum => $cust_main->agentnum } );
29   if (@count > 1) {
30     note("DELETING CARDTYPE GATEWAYS");
31     foreach my $apg (@count) {
32       $err = $apg->delete if $apg->cardtype;
33       last if $err;
34     }
35     @count = $fs->qsearch('agent_payment_gateway', { agentnum => $cust_main->agentnum } );
36     if (@count > 1) {
37       $err = "Still found ".@count." gateways for custnum ".$cust_main->custnum;
38       last;
39     }
40   }
41 }
42 ok( !$err, "remove obsolete payment gateways" ) or BAIL_OUT($err);
43
44 $bopconf = 
45 'IPPay
46 TESTTERMINAL';
47 $conf->set('business-onlinepayment' => $bopconf);
48 is( join("\n",$conf->config('business-onlinepayment')), $bopconf, "setting first default gateway" ) or BAIL_OUT('');
49
50 # generate a few void/refund records for upgrading
51 my $counter = 20;
52 foreach my $cust_pay ( $fs->qsearch('cust_pay',{ payby => 'CARD' }) ) {
53   if ($counter % 2) {
54     $err = $cust_pay->void('Testing');
55     $err = "Voiding: $err" if $err;
56   } else {
57     # from realtime_refund_bop, just the important bits    
58     while ( $cust_pay->unapplied < $cust_pay->paid ) {
59       my @cust_bill_pay = $cust_pay->cust_bill_pay;
60       last unless @cust_bill_pay;
61       my $cust_bill_pay = pop @cust_bill_pay;
62       $err = $cust_bill_pay->delete;
63       $err = "Refund unapply: $err" if $err;
64       last if $err;
65     }
66     last if $err;
67     my $cust_refund = new FS::cust_refund ( {
68       'custnum'  => $cust_pay->cust_main->custnum,
69       'paynum'   => $cust_pay->paynum,
70       'source_paynum' => $cust_pay->paynum,
71       'refund'   => $cust_pay->paid,
72       '_date'    => '',
73       'payby'    => $cust_pay->payby,
74       'payinfo'  => $cust_pay->payinfo,
75       'reason'     => 'Testing',
76       'gatewaynum'    => $cust_pay->gatewaynum,
77       'processor'     => $cust_pay->payment_gateway ? $cust_pay->payment_gateway->processor : '',
78       'auth'          => $cust_pay->auth,
79       'order_number'  => $cust_pay->order_number,
80     } );
81     $err = $cust_refund->insert( reason_type => 'Refund' );
82     $err = "Refunding: $err" if $err;
83   }
84   last if $err;
85   $counter -= 1;
86   last unless $counter > 0;
87 }
88 ok( !$err, "create some refunds and voids" ) or BAIL_OUT($err);
89
90 # also, just to test behavior in this case, create a record for an aborted
91 # verification payment. this will have no customer number.
92
93 my $pending_failed = FS::cust_pay_pending->new({
94   'custnum_pending' => 1,
95   'paid'    => '1.00',
96   '_date'   => time - 86400,
97   random_card(),
98   'status'  => 'failed',
99   'statustext' => 'Tokenization upgrade test',
100 });
101 $err = $pending_failed->insert;
102 ok( !$err, "create a failed payment attempt" ) or BAIL_OUT($err);
103
104 # find two stored credit cards.
105 my @cust = map { FS::cust_main->by_key($_) } (10, 12);
106 my @payby = map { ($_->cust_payby)[0] } @cust;
107 my @payment;
108
109 ok( $payby[0]->payby eq 'CARD' && !$payby[0]->tokenized,
110   "first customer has a non-tokenized card"
111   ) or BAIL_OUT();
112
113 $err = $cust[0]->realtime_cust_payby(amount => '2.00');
114 ok( !$err, "create a payment through IPPay" )
115   or BAIL_OUT($err);
116 $payment[0] = $fs->qsearchs('cust_pay', { custnum => $cust[0]->custnum,
117                                      paid => '2.00' })
118   or BAIL_OUT("can't find payment record");
119
120 $err = system('freeside-upgrade','admin');
121 ok( !$err, 'initial upgrade' ) or BAIL_OUT('Error string: '.$!);
122
123 # switch to CardFortress
124 $bopconf =
125 'CardFortress
126 cardfortresstest
127 (TEST54)
128 Normal Authorization
129 gateway
130 IPPay
131 gateway_login
132 TESTTERMINAL
133 gateway_password
134
135 private_key
136 /usr/local/etc/freeside/cardfortresstest.txt';
137 $conf->set('business-onlinepayment' => $bopconf);
138 is( join("\n",$conf->config('business-onlinepayment')), $bopconf, "setting tokenizable default gateway" ) or BAIL_OUT('');
139
140 foreach my $pg ($fs->qsearch('payment_gateway')) {
141   unless ($pg->gateway_module eq 'CardFortress') {
142     note('UPGRADING NON-CF PAYMENT GATEWAY');
143     my %pgopts = (
144       gateway          => $pg->gateway_module,
145       gateway_login    => $pg->gateway_username,
146       gateway_password => $pg->gateway_password,
147       private_key      => '/usr/local/etc/freeside/cardfortresstest.txt',
148     );
149     $pg->gateway_module('CardFortress');
150     $pg->gateway_username('cardfortresstest');
151     $pg->gateway_password('(TEST54)');
152     $err = $pg->replace(\%pgopts);
153     last if $err;
154   }
155 }
156 ok( !$err, "remove non-CF payment gateways" ) or BAIL_OUT($err);
157
158 # create a payment using a non-tokenized card. this should immediately
159 # trigger tokenization.
160 ok( $payby[1]->payby eq 'CARD' && ! $payby[1]->tokenized,
161   "second customer has a non-tokenized card"
162   ) or BAIL_OUT();
163
164 $err = $cust[1]->realtime_cust_payby(amount => '3.00');
165 ok( !$err, "tokenize a card when it's first used for payment" )
166   or BAIL_OUT($err);
167 $payment[1] = $fs->qsearchs('cust_pay', { custnum => $cust[1]->custnum,
168                                      paid => '3.00' })
169   or BAIL_OUT("can't find payment record");
170 ok( $payment[1]->tokenized, "payment is tokenized" );
171 $payby[1] = $payby[1]->replace_old;
172 ok( $payby[1]->tokenized, "card is now tokenized" );
173
174 # invoke the part of freeside-upgrade that tokenizes
175 FS::cust_main->queueable_upgrade();
176 #$err = system('freeside-upgrade','admin');
177 #ok( !$err, 'tokenizable upgrade' ) or BAIL_OUT('Error string: '.$!);
178
179 $payby[0] = $payby[0]->replace_old;
180 ok( $payby[0]->tokenized, "old card was tokenized during upgrade" );
181 $payment[0] = $payment[0]->replace_old;
182 ok( $payment[0]->tokenized, "old payment was tokenized during upgrade" );
183 ok( ($payment[0]->cust_pay_pending)[0]->tokenized, "old cust_pay_pending was tokenized during upgrade" );
184
185 $pending_failed = $pending_failed->replace_old;
186 ok( $pending_failed->tokenized, "cust_pay_pending with no customer was tokenized" );
187
188 # add a new payment card to one customer
189 $payby[2] = FS::cust_payby->new({
190   custnum => $cust[0]->custnum,
191   random_card(),
192 });
193 $err = $payby[2]->insert;
194 ok( !$err, "new card was saved" );
195 ok($payby[2]->tokenized, "new card is tokenized" );
196
197 sub random_card {
198   my $payinfo = '4111' . join('', map { int(rand(10)) } 1 .. 11);
199   $payinfo .= generate_last_digit($payinfo);
200   my $paydate = DateTime->now
201                 ->add('years' => 1)
202                 ->truncate(to => 'month')
203                 ->strftime('%F');
204   return ( 'payby'    => 'CARD',
205            'payinfo'  => $payinfo,
206            'paydate'  => $paydate,
207            'payname'  => 'Tokenize Me',
208   );
209 }
210
211
212 1;
213