finish fixing "Real time processing not enabled!" error when using signup
[freeside.git] / FS / FS / ClientAPI / Signup.pm
1 package FS::ClientAPI::Signup;
2
3 use strict;
4 use vars qw($DEBUG $me);
5 use Data::Dumper;
6 use Tie::RefHash;
7 use FS::Conf;
8 use FS::Record qw(qsearch qsearchs dbdef);
9 use FS::CGI qw(popurl);
10 use FS::Msgcat qw(gettext);
11 use FS::Misc qw(card_types);
12 use FS::ClientAPI_SessionCache;
13 use FS::agent;
14 use FS::cust_main_county;
15 use FS::part_pkg;
16 use FS::svc_acct_pop;
17 use FS::cust_main;
18 use FS::cust_pkg;
19 use FS::svc_acct;
20 use FS::svc_phone;
21 use FS::acct_snarf;
22 use FS::queue;
23 use FS::reg_code;
24 use FS::payby;
25
26 $DEBUG = 0;
27 $me = '[FS::ClientAPI::Signup]';
28
29 sub signup_info {
30   my $packet = shift;
31
32   warn "$me signup_info called on $packet\n" if $DEBUG;
33
34   my $conf = new FS::Conf;
35   my $svc_x = $conf->config('signup_server-service') || 'svc_acct';
36
37   my $cache = new FS::ClientAPI_SessionCache( {
38     'namespace' => 'FS::ClientAPI::Signup',
39   } );
40   my $signup_info_cache = $cache->get('signup_info_cache');
41
42   if ( $signup_info_cache ) {
43
44     warn "$me loading cached signup info\n" if $DEBUG > 1;
45
46   } else {
47
48     warn "$me populating signup info cache\n" if $DEBUG > 1;
49
50     my $agentnum2part_pkg = 
51       {
52         map {
53           my $agent = $_;
54           my $href = $agent->pkgpart_hashref;
55           $agent->agentnum =>
56             [
57               map { { 'payby'       => [ $_->payby ],
58                       'freq_pretty' => $_->freq_pretty,
59                       'options'     => { $_->options },
60                       %{$_->hashref}
61                   } }
62                 grep { $_->svcpart($svc_x)
63                        && ( $href->{ $_->pkgpart }
64                             || ( $_->agentnum
65                                  && $_->agentnum == $agent->agentnum
66                                )
67                           )
68                      }
69                   qsearch( 'part_pkg', { 'disabled' => '' } )
70             ];
71         } qsearch('agent', { 'disabled' => '' })
72       };
73
74     my $msgcat = { map { $_=>gettext($_) }
75                        qw( passwords_dont_match invalid_card unknown_card_type
76                            not_a empty_password illegal_or_empty_text )
77                  };
78     warn "msgcat: ". Dumper($msgcat). "\n" if $DEBUG > 2;
79
80     my $label = { map { $_ => FS::Msgcat::_gettext($_) }
81                       qw( stateid stateid_state )
82                 };
83     warn "label: ". Dumper($label). "\n" if $DEBUG > 2;
84
85     my @agent_fields = qw( agentnum agent );
86
87     $signup_info_cache = {
88       'cust_main_county' => [ map $_->hashref,
89                                   qsearch('cust_main_county', {} )
90                             ],
91
92       'agent' => [ map { my $agent = $_;
93                          map { $_ => $agent->get($_) } @agent_fields;
94                        }
95                        qsearch('agent', { 'disabled' => '' } )
96                  ],
97
98       'part_referral' => [ map $_->hashref,
99                                qsearch('part_referral', { 'disabled' => '' } )
100                          ],
101
102       'agentnum2part_pkg' => $agentnum2part_pkg,
103
104       'svc_acct_pop' => [ map $_->hashref, qsearch('svc_acct_pop',{} ) ],
105
106       'emailinvoiceonly' => $conf->exists('emailinvoiceonly'),
107
108       'security_phrase' => $conf->exists('security_phrase'),
109
110       'payby' => [ $conf->config('signup_server-payby') ],
111
112       'card_types' => card_types(),
113
114       'paytypes' => [ @FS::cust_main::paytypes ],
115
116       'cvv_enabled' => 1,
117
118       'stateid_enabled' => $conf->exists('show_stateid'),
119
120       'paystate_enabled' => $conf->exists('show_bankstate'),
121
122       'ship_enabled' => 1,
123
124       'msgcat' => $msgcat,
125
126       'label' => $label,
127
128       'statedefault' => scalar($conf->config('statedefault')) || 'CA',
129
130       'countrydefault' => scalar($conf->config('countrydefault')) || 'US',
131
132       'refnum' => scalar($conf->config('signup_server-default_refnum')),
133
134       'default_pkgpart' => scalar($conf->config('signup_server-default_pkgpart')),
135
136       'signup_service' => $svc_x,
137       'default_svcpart' => scalar($conf->config('signup_server-default_svcpart')),
138
139       'head'         => join("\n", $conf->config('selfservice-head') ),
140       'body_header'  => join("\n", $conf->config('selfservice-body_header') ),
141       'body_footer'  => join("\n", $conf->config('selfservice-body_footer') ),
142       'body_bgcolor' => scalar( $conf->config('selfservice-body_bgcolor') ),
143       'box_bgcolor'  => scalar( $conf->config('selfservice-box_bgcolor')  ),
144
145       'company_name'   => scalar($conf->config('company_name')),
146
147       #per-agent?
148       'agent_ship_address' => scalar($conf->exists('agent-ship_address')),
149
150       'no_company'        => scalar($conf->exists('signup-no_company')),
151       'require_phone'     => scalar($conf->exists('cust_main-require_phone')),
152       'recommend_daytime' => scalar($conf->exists('signup-recommend_daytime')),
153       'recommend_email'   => scalar($conf->exists('signup-recommend_email')),
154
155     };
156
157     $cache->set('signup_info_cache', $signup_info_cache);
158
159   }
160
161   my $signup_info = { %$signup_info_cache };
162   warn "$me signup info loaded\n" if $DEBUG > 1;
163   warn Dumper($signup_info). "\n" if $DEBUG > 2;
164
165   my @addl = qw( signup_server-classnum2 signup_server-classnum3 );
166
167   if ( grep { $conf->exists($_) } @addl ) {
168   
169     $signup_info->{optional_packages} = [];
170
171     foreach my $addl ( @addl ) {
172
173       warn "$me adding optional package info\n" if $DEBUG > 1;
174
175       my $classnum = $conf->config($addl) or next;
176
177       my @pkgs = map { {
178                          'freq_pretty' => $_->freq_pretty,
179                          'options'     => { $_->options },
180                          %{ $_->hashref }
181                        };
182                      }
183                      qsearch( 'part_pkg', { classnum => $classnum } );
184
185       push @{$signup_info->{optional_packages}}, \@pkgs;
186
187       warn "$me done adding opt. package info for $classnum\n" if $DEBUG > 1;
188
189     }
190
191   }
192
193   my $agentnum = $packet->{'agentnum'}
194                  || $conf->config('signup_server-default_agentnum');
195   $agentnum =~ /^(\d*)$/ or die "illegal agentnum";
196   $agentnum = $1;
197
198   my $session = '';
199   if ( exists $packet->{'session_id'} ) {
200
201     warn "$me loading agent session\n" if $DEBUG > 1;
202     my $cache = new FS::ClientAPI_SessionCache( {
203       'namespace' => 'FS::ClientAPI::Agent',
204     } );
205     $session = $cache->get($packet->{'session_id'});
206     if ( $session ) {
207       $agentnum = $session->{'agentnum'};
208     } else {
209       return { 'error' => "Can't resume session" }; #better error message
210     }
211     warn "$me done loading agent session\n" if $DEBUG > 1;
212
213   } elsif ( exists $packet->{'customer_session_id'} ) {
214
215     warn "$me loading customer session\n" if $DEBUG > 1;
216     my $cache = new FS::ClientAPI_SessionCache( {
217       'namespace' => 'FS::ClientAPI::MyAccount',
218     } );
219     $session = $cache->get($packet->{'customer_session_id'});
220     if ( $session ) {
221       my $custnum = $session->{'custnum'};
222       my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum });
223       return { 'error' => "Can't find your customer record" } unless $cust_main;
224       $agentnum = $cust_main->agentnum;
225     } else {
226       return { 'error' => "Can't resume session" }; #better error message
227     }
228     warn "$me done loading customer session\n" if $DEBUG > 1;
229
230   }
231
232   $signup_info->{'part_pkg'} = [];
233
234   if ( $packet->{'reg_code'} ) {
235
236     warn "$me setting package list via reg_code\n" if $DEBUG > 1;
237
238     $signup_info->{'part_pkg'} = 
239       [ map { { 'payby'       => [ $_->payby ],
240                 'freq_pretty' => $_->freq_pretty,
241                 'options'     => { $_->options },
242                 %{$_->hashref}
243               };
244             }
245           grep { $_->svcpart($svc_x) }
246           map { $_->part_pkg }
247             qsearchs( 'reg_code', { 'code'     => $packet->{'reg_code'},
248                                     'agentnum' => $agentnum,              } )
249
250       ];
251
252     $signup_info->{'error'} = 'Unknown registration code'
253       unless @{ $signup_info->{'part_pkg'} };
254
255     warn "$me done setting package list via reg_code\n" if $DEBUG > 1;
256
257   } elsif ( $packet->{'promo_code'} ) {
258
259     warn "$me setting package list via promo_code\n" if $DEBUG > 1;
260
261     $signup_info->{'part_pkg'} =
262       [ map { { 'payby'   => [ $_->payby ],
263                 'freq_pretty' => $_->freq_pretty,
264                 'options'     => { $_->options },
265                 %{$_->hashref}
266             } }
267           grep { $_->svcpart($svc_x) }
268             qsearch( 'part_pkg', { 'promo_code' => {
269                                      op=>'ILIKE',
270                                      value=>$packet->{'promo_code'}
271                                    },
272                                    'disabled'   => '',                  } )
273       ];
274
275     $signup_info->{'error'} = 'Unknown promotional code'
276       unless @{ $signup_info->{'part_pkg'} };
277
278     warn "$me done setting package list via promo_code\n" if $DEBUG > 1;
279   }
280
281   if ( $agentnum ) {
282
283     warn "$me setting agent-specific payment flag\n" if $DEBUG > 1;
284     my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
285     warn "$me has agent $agent\n" if $DEBUG > 1;
286     if ( $agent ) { #else complain loudly?
287       $signup_info->{'hide_payment_fields'} = [];
288       foreach my $payby (@{$signup_info->{payby}}) {
289         warn "$me checking $payby payment fields\n" if $DEBUG > 1;
290         my $hide = 0;
291         if ( FS::payby->realtime($payby) ) {
292           my $payment_gateway =
293             $agent->payment_gateway( 'method'  => FS::payby->payby2bop($payby),
294                                      'nofatal' => 1,
295                                    );
296           if ( $payment_gateway
297                  && $payment_gateway->gateway_namespace
298                       eq 'Business::OnlineThirdPartyPayment'
299              ) {
300             warn "$me hiding $payby payment fields\n" if $DEBUG > 1;
301             $hide = 1;
302           }
303         }
304         push @{$signup_info->{'hide_payment_fields'}}, $hide;
305       }
306     }
307     warn "$me done setting agent-specific payment flag\n" if $DEBUG > 1;
308
309     warn "$me setting agent-specific package list\n" if $DEBUG > 1;
310     $signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum}
311       unless @{ $signup_info->{'part_pkg'} };
312     warn "$me done setting agent-specific package list\n" if $DEBUG > 1;
313
314     warn "$me setting agent-specific adv. source list\n" if $DEBUG > 1;
315     $signup_info->{'part_referral'} =
316       [
317         map { $_->hashref }
318           qsearch( {
319                      'table'     => 'part_referral',
320                      'hashref'   => { 'disabled' => '' },
321                      'extra_sql' => "AND (    agentnum = $agentnum  ".
322                                     "      OR agentnum IS NULL    ) ",
323                    },
324                  )
325       ];
326     warn "$me done setting agent-specific adv. source list\n" if $DEBUG > 1;
327
328     $signup_info->{'agent_name'} = $agent->agent;
329
330     $signup_info->{'company_name'} = $conf->config('company_name', $agentnum);
331
332     if ( $signup_info->{'agent_ship_address'} && $agent->agent_custnum ) {
333       my $cust_main = $agent->agent_cust_main;
334       my $prefix = length($cust_main->ship_last) ? 'ship_' : '';
335       $signup_info->{"ship_$_"} = $cust_main->get("$prefix$_")
336         foreach qw( address1 city county state zip country );
337     }
338
339   }
340   # else {
341   # delete $signup_info->{'part_pkg'};
342   #}
343
344   warn "$me sorting package list\n" if $DEBUG > 1;
345   $signup_info->{'part_pkg'} = [ sort { $a->{pkg} cmp $b->{pkg} }  # case?
346                                       @{ $signup_info->{'part_pkg'} }
347                                ];
348   warn "$me done sorting package list\n" if $DEBUG > 1;
349
350   if ( exists $packet->{'session_id'} ) {
351     my $agent_signup_info = { %$signup_info };
352     delete $agent_signup_info->{agentnum2part_pkg};
353     $agent_signup_info->{'agent'} = $session->{'agent'};
354     $agent_signup_info;
355   } else {
356     $signup_info;
357   }
358
359 }
360
361 sub domain_select_hash {
362   my $packet = shift;
363
364   my $response = {};
365
366   if ($packet->{pkgpart}) {
367     my $part_pkg = qsearchs('part_pkg' => { 'pkgpart' => $packet->{pkgpart} } );
368     #$packet->{svcpart} = $part_pkg->svcpart('svc_acct')
369     $packet->{svcpart} = $part_pkg->svcpart
370       if $part_pkg;
371   }
372
373   if ($packet->{svcpart}) {
374     my $part_svc = qsearchs('part_svc' => { 'svcpart' => $packet->{svcpart} } );
375     $response->{'domsvc'} = $part_svc->part_svc_column('domsvc')->columnvalue
376       if ($part_svc && $part_svc->part_svc_column('domsvc')->columnflag  eq 'D');
377   }
378
379   $response->{'domains'}
380     = { domain_select_hash FS::svc_acct( map { $_ => $packet->{$_} }
381                                                  qw(svcpart pkgnum)
382                                        ) };
383
384   $response;
385 }
386
387 sub new_customer {
388   my $packet = shift;
389
390   my $conf = new FS::Conf;
391   my $svc_x = $conf->config('signup_server-service') || 'svc_acct';
392
393   if ( $svc_x eq 'svc_acct' ) {
394   
395     #things that aren't necessary in base class, but are for signup server
396       #return "Passwords don't match"
397       #  if $hashref->{'_password'} ne $hashref->{'_password2'}
398     return { 'error' => gettext('empty_password') }
399       unless length($packet->{'_password'});
400     # a bit inefficient for large numbers of pops
401     return { 'error' => gettext('no_access_number_selected') }
402       unless $packet->{'popnum'} || !scalar(qsearch('svc_acct_pop',{} ));
403
404   }
405
406   my $agentnum;
407   if ( exists $packet->{'session_id'} ) {
408     my $cache = new FS::ClientAPI_SessionCache( {
409       'namespace' => 'FS::ClientAPI::Agent',
410     } );
411     my $session = $cache->get($packet->{'session_id'});
412     if ( $session ) {
413       $agentnum = $session->{'agentnum'};
414     } else {
415       return { 'error' => "Can't resume session" }; #better error message
416     }
417   } else {
418     $agentnum = $packet->{agentnum}
419                 || $conf->config('signup_server-default_agentnum');
420   }
421
422   #shares some stuff with htdocs/edit/process/cust_main.cgi... take any
423   # common that are still here and library them.
424   my $cust_main = new FS::cust_main ( {
425     #'custnum'          => '',
426     'agentnum'      => $agentnum,
427     'refnum'        => $packet->{refnum}
428                        || $conf->config('signup_server-default_refnum'),
429
430     map { $_ => $packet->{$_} } qw(
431
432       last first ss company address1 address2
433       city county state zip country
434       daytime night fax stateid stateid_state
435
436       ship_last ship_first ship_ss ship_company ship_address1 ship_address2
437       ship_city ship_county ship_state ship_zip ship_country
438       ship_daytime ship_night ship_fax
439
440       payby
441       payinfo paycvv paydate payname paystate paytype
442       paystart_month paystart_year payissue
443       payip
444
445       referral_custnum comments
446     )
447
448   } );
449
450   my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
451   if ( $conf->exists('agent_ship_address') && $agent->agent_custnum ) {
452     my $agent_cust_main = $agent->agent_cust_main;
453     my $prefix = length($agent_cust_main->ship_last) ? 'ship_' : '';
454     $cust_main->set("ship_$_", $agent_cust_main->get("$prefix$_") )
455       foreach qw( address1 city county state zip country );
456
457     $cust_main->set("ship_$_", $cust_main->get($_))
458       foreach qw( last first );
459
460   }
461
462
463   return { 'error' => "Illegal payment type" }
464     unless grep { $_ eq $packet->{'payby'} }
465                 $conf->config('signup_server-payby');
466
467   if (FS::payby->realtime($packet->{payby})) {
468     my $payby = $packet->{payby};
469
470     my $agent = qsearchs('agent', { 'agentnum' => $agentnum });
471     return { 'error' => "Unknown reseller" }
472       unless $agent;
473
474     my $payment_gateway =
475       $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby) );
476
477     if ($payment_gateway->gateway_namespace eq
478         'Business::OnlineThirdPartyPayment'
479        ) {
480       $cust_main->payby('BILL');   # MCRD better?
481     }
482   }
483
484   $cust_main->payinfo($cust_main->daytime)
485     if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
486
487   my @invoicing_list = $packet->{'invoicing_list'}
488                          ? split( /\s*\,\s*/, $packet->{'invoicing_list'} )
489                          : ();
490
491   $packet->{'pkgpart'} =~ /^(\d+)$/ or '' =~ /^()$/;
492   my $pkgpart = $1;
493   return { 'error' => 'Please select a package' } unless $pkgpart; #msgcat
494
495   my $part_pkg =
496     qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
497       or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" };
498   my $svcpart = $part_pkg->svcpart($svc_x);
499
500   my $reg_code = '';
501   if ( $packet->{'reg_code'} ) {
502     $reg_code = qsearchs( 'reg_code', { 'code'     => $packet->{'reg_code'},
503                                         'agentnum' => $agentnum,             } )
504       or return { 'error' => 'Unknown registration code' };
505   }
506
507   my $cust_pkg = new FS::cust_pkg ( {
508     #later#'custnum' => $custnum,
509     'pkgpart'    => $packet->{'pkgpart'},
510     'promo_code' => $packet->{'promo_code'},
511     'reg_code'   => $packet->{'reg_code'},
512   } );
513   #my $error = $cust_pkg->check;
514   #return { 'error' => $error } if $error;
515
516   #should be all auto-magic and shit
517   my $svc;
518   if ( $svc_x eq 'svc_acct' ) {
519
520     $svc = new FS::svc_acct ( {
521       'svcpart'   => $svcpart,
522       map { $_ => $packet->{$_} }
523         qw( username _password sec_phrase popnum ),
524     } );
525
526     my @acct_snarf;
527     my $snarfnum = 1;
528     while (    exists($packet->{"snarf_machine$snarfnum"})
529             && length($packet->{"snarf_machine$snarfnum"}) ) {
530       my $acct_snarf = new FS::acct_snarf ( {
531         'machine'   => $packet->{"snarf_machine$snarfnum"},
532         'protocol'  => $packet->{"snarf_protocol$snarfnum"},
533         'username'  => $packet->{"snarf_username$snarfnum"},
534         '_password' => $packet->{"snarf_password$snarfnum"},
535       } );
536       $snarfnum++;
537       push @acct_snarf, $acct_snarf;
538     }
539     $svc->child_objects( \@acct_snarf );
540
541   } elsif ( $svc_x eq 'svc_phone' ) {
542
543     $svc = new FS::svc_phone ( {
544       'svcpart' => $svcpart,
545        map { $_ => $packet->{$_} }
546          qw( countrycode phonenum sip_password pin ),
547     } );
548
549   } else {
550     die "unknown signup service $svc_x";
551   }
552
553   my $y = $svc->setdefault; # arguably should be in new method
554   return { 'error' => $y } if $y && !ref($y);
555
556   #$error = $svc->check;
557   #return { 'error' => $error } if $error;
558
559   #setup a job dependancy to delay provisioning
560   my $placeholder = new FS::queue ( {
561     'job'    => 'FS::ClientAPI::Signup::__placeholder',
562     'status' => 'locked',
563   } );
564   my $error = $placeholder->insert;
565   return { 'error' => $error } if $error;
566
567   use Tie::RefHash;
568   tie my %hash, 'Tie::RefHash';
569   %hash = ( $cust_pkg => [ $svc ] );
570   #msgcat
571   $error = $cust_main->insert(
572     \%hash,
573     \@invoicing_list,
574     'depend_jobnum' => $placeholder->jobnum,
575   );
576   if ( $error ) {
577     my $perror = $placeholder->delete;
578     $error .= " (Additionally, error removing placeholder: $perror)" if $perror;
579     return { 'error' => $error };
580   }
581
582   if ( $conf->exists('signup_server-realtime') ) {
583
584     #warn "[fs_signup_server] Billing customer...\n" if $Debug;
585
586     my $bill_error = $cust_main->bill;
587     #warn "[fs_signup_server] error billing new customer: $bill_error"
588     #  if $bill_error;
589
590     $bill_error = $cust_main->apply_payments_and_credits;
591     #warn "[fs_signup_server] error applying payments and credits for".
592     #     " new customer: $bill_error"
593     #  if $bill_error;
594
595     if ($cust_main->_new_bop_required()) {
596       $bill_error = $cust_main->realtime_collect(
597          method        => FS::payby->payby2bop( $packet->{payby} ),
598          depend_jobnum => $placeholder->jobnum,
599       );
600     } else {
601       $bill_error = $cust_main->collect('realtime' => 1);
602     }
603     #warn "[fs_signup_server] error collecting from new customer: $bill_error"
604     #  if $bill_error;
605
606     if ($bill_error && ref($bill_error) eq 'HASH') {
607       return { 'error' => '_collect',
608                ( map { $_ => $bill_error->{$_} }
609                  qw(popup_url reference collectitems)
610                ),
611                amount => $cust_main->balance,
612              };
613     }
614
615     if ( $cust_main->balance > 0 ) {
616
617       #this makes sense.  credit is "un-doing" the invoice
618       $cust_main->credit( $cust_main->balance, 'signup server decline',
619                           'reason_type' => $conf->config('signup_credit_type'),
620                         );
621       $cust_main->apply_credits;
622
623       #should check list for errors...
624       #$cust_main->suspend;
625       local $FS::svc_Common::noexport_hack = 1;
626       $cust_main->cancel('quiet'=>1);
627
628       my $perror = $placeholder->depended_delete;
629       warn "error removing provisioning jobs after decline: $perror" if $perror;
630       unless ( $perror ) {
631         $perror = $placeholder->delete;
632         warn "error removing placeholder after decline: $perror" if $perror;
633       }
634
635       return { 'error' => '_decline' };
636     }
637
638   }
639
640   if ( $reg_code ) {
641     $error = $reg_code->delete;
642     return { 'error' => $error } if $error;
643   }
644
645   $error = $placeholder->delete;
646   return { 'error' => $error } if $error;
647
648   my %return = ( 'error'          => '',
649                  'signup_service' => $svc_x,
650                );
651
652   if ( $svc_x eq 'svc_acct' ) {
653     $return{$_} = $svc->$_() for qw( username _password );
654   } elsif ( $svc_x eq 'svc_phone' ) {
655     $return{$_} = $svc->$_() for qw( countrycode phonenum sip_password pin );
656   } else {
657     die "unknown signup service $svc_x";
658   }
659
660   return \%return;
661
662 }
663
664 sub capture_payment {
665   my $packet = shift;
666
667   warn "$me capture_payment called on $packet\n" if $DEBUG;
668
669   ###
670   # identify processor/gateway from called back URL
671   ###
672
673   my $conf = new FS::Conf;
674
675   my $url = $packet->{url};
676   my $payment_gateway =
677     qsearchs('payment_gateway', { 'gateway_callback_url' => popurl(0, $url) } );
678
679   unless ($payment_gateway) {
680
681     my ( $processor, $login, $password, $action, @bop_options ) =
682       $conf->config('business-onlinepayment');
683     $action ||= 'normal authorization';
684     pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
685     die "No real-time processor is enabled - ".
686         "did you set the business-onlinepayment configuration value?\n"
687       unless $processor;
688
689     $payment_gateway = new FS::payment_gateway( {
690       gateway_namespace => $conf->config('business-onlinepayment-namespace'),
691       gateway_module    => $processor,
692       gateway_username  => $login,
693       gateway_password  => $password,
694       gateway_action    => $action,
695       options   => [ ( @bop_options ) ],
696     });
697
698   }
699  
700   die "No real-time third party processor is enabled - ".
701       "did you set the business-onlinepayment configuration value?\n*"
702     unless $payment_gateway->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
703
704   ###
705   # locate pending transaction
706   ###
707
708   eval "use Business::OnlineThirdPartyPayment";
709   die $@ if $@;
710
711   my $transaction =
712     new Business::OnlineThirdPartyPayment( $payment_gateway->gateway_module,
713                                            @{ [ $payment_gateway->options ] },
714                                          );
715
716   my $paypendingnum = $transaction->reference($packet->{data});
717
718   my $cust_pay_pending =
719     qsearchs('cust_pay_pending', { paypendingnum => $paypendingnum } );
720
721   unless ($cust_pay_pending) {
722     my $bill_error = "No payment is being processed with id $paypendingnum".
723                      "; Transaction aborted.";
724     return { error => '_decline', bill_error => $bill_error };
725   }
726
727   if ($cust_pay_pending->status ne 'pending') {
728     my $bill_error = "Payment with id $paypendingnum is not pending, but ".
729                      $cust_pay_pending->status.  "; Transaction aborted.";
730     return { error => '_decline', bill_error => $bill_error };
731   }
732
733   my $cust_main = $cust_pay_pending->cust_main;
734   my $bill_error =
735     $cust_main->realtime_botpp_capture( $cust_pay_pending, %{$packet->{data}} );
736
737   return { 'error'      => ( $bill_error->{bill_error} ? '_decline' : '' ),
738            %$bill_error,
739          };
740
741 }
742
743 1;