eliminate harmless warning log spam:
[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           if ( $payment_gateway
295                  && $payment_gateway->gateway_namespace
296                       eq 'Business::OnlineThirdPartyPayment'
297              ) {
298             warn "$me hiding $payby payment fields\n" if $DEBUG > 1;
299             $hide = 1;
300           }
301         }
302         push @{$signup_info->{'hide_payment_fields'}}, $hide;
303       }
304     }
305     warn "$me done setting agent-specific payment flag\n" if $DEBUG > 1;
306
307     warn "$me setting agent-specific package list\n" if $DEBUG > 1;
308     $signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum}
309       unless @{ $signup_info->{'part_pkg'} };
310     warn "$me done setting agent-specific package list\n" if $DEBUG > 1;
311
312     warn "$me setting agent-specific adv. source list\n" if $DEBUG > 1;
313     $signup_info->{'part_referral'} =
314       [
315         map { $_->hashref }
316           qsearch( {
317                      'table'     => 'part_referral',
318                      'hashref'   => { 'disabled' => '' },
319                      'extra_sql' => "AND (    agentnum = $agentnum  ".
320                                     "      OR agentnum IS NULL    ) ",
321                    },
322                  )
323       ];
324     warn "$me done setting agent-specific adv. source list\n" if $DEBUG > 1;
325
326     $signup_info->{'agent_name'} = $agent->agent;
327
328     $signup_info->{'company_name'} = $conf->config('company_name', $agentnum);
329
330     if ( $signup_info->{'agent_ship_address'} && $agent->agent_custnum ) {
331       my $cust_main = $agent->agent_cust_main;
332       my $prefix = length($cust_main->ship_last) ? 'ship_' : '';
333       $signup_info->{"ship_$_"} = $cust_main->get("$prefix$_")
334         foreach qw( address1 city county state zip country );
335     }
336
337   }
338   # else {
339   # delete $signup_info->{'part_pkg'};
340   #}
341
342   warn "$me sorting package list\n" if $DEBUG > 1;
343   $signup_info->{'part_pkg'} = [ sort { $a->{pkg} cmp $b->{pkg} }  # case?
344                                       @{ $signup_info->{'part_pkg'} }
345                                ];
346   warn "$me done sorting package list\n" if $DEBUG > 1;
347
348   if ( exists $packet->{'session_id'} ) {
349     my $agent_signup_info = { %$signup_info };
350     delete $agent_signup_info->{agentnum2part_pkg};
351     $agent_signup_info->{'agent'} = $session->{'agent'};
352     $agent_signup_info;
353   } else {
354     $signup_info;
355   }
356
357 }
358
359 sub domain_select_hash {
360   my $packet = shift;
361
362   my $response = {};
363
364   if ($packet->{pkgpart}) {
365     my $part_pkg = qsearchs('part_pkg' => { 'pkgpart' => $packet->{pkgpart} } );
366     #$packet->{svcpart} = $part_pkg->svcpart('svc_acct')
367     $packet->{svcpart} = $part_pkg->svcpart
368       if $part_pkg;
369   }
370
371   if ($packet->{svcpart}) {
372     my $part_svc = qsearchs('part_svc' => { 'svcpart' => $packet->{svcpart} } );
373     $response->{'domsvc'} = $part_svc->part_svc_column('domsvc')->columnvalue
374       if ($part_svc && $part_svc->part_svc_column('domsvc')->columnflag  eq 'D');
375   }
376
377   $response->{'domains'}
378     = { domain_select_hash FS::svc_acct( map { $_ => $packet->{$_} }
379                                                  qw(svcpart pkgnum)
380                                        ) };
381
382   $response;
383 }
384
385 sub new_customer {
386   my $packet = shift;
387
388   my $conf = new FS::Conf;
389   my $svc_x = $conf->config('signup_server-service') || 'svc_acct';
390
391   if ( $svc_x eq 'svc_acct' ) {
392   
393     #things that aren't necessary in base class, but are for signup server
394       #return "Passwords don't match"
395       #  if $hashref->{'_password'} ne $hashref->{'_password2'}
396     return { 'error' => gettext('empty_password') }
397       unless length($packet->{'_password'});
398     # a bit inefficient for large numbers of pops
399     return { 'error' => gettext('no_access_number_selected') }
400       unless $packet->{'popnum'} || !scalar(qsearch('svc_acct_pop',{} ));
401
402   }
403
404   my $agentnum;
405   if ( exists $packet->{'session_id'} ) {
406     my $cache = new FS::ClientAPI_SessionCache( {
407       'namespace' => 'FS::ClientAPI::Agent',
408     } );
409     my $session = $cache->get($packet->{'session_id'});
410     if ( $session ) {
411       $agentnum = $session->{'agentnum'};
412     } else {
413       return { 'error' => "Can't resume session" }; #better error message
414     }
415   } else {
416     $agentnum = $packet->{agentnum}
417                 || $conf->config('signup_server-default_agentnum');
418   }
419
420   #shares some stuff with htdocs/edit/process/cust_main.cgi... take any
421   # common that are still here and library them.
422   my $cust_main = new FS::cust_main ( {
423     #'custnum'          => '',
424     'agentnum'      => $agentnum,
425     'refnum'        => $packet->{refnum}
426                        || $conf->config('signup_server-default_refnum'),
427
428     map { $_ => $packet->{$_} } qw(
429
430       last first ss company address1 address2
431       city county state zip country
432       daytime night fax stateid stateid_state
433
434       ship_last ship_first ship_ss ship_company ship_address1 ship_address2
435       ship_city ship_county ship_state ship_zip ship_country
436       ship_daytime ship_night ship_fax
437
438       payby
439       payinfo paycvv paydate payname paystate paytype
440       paystart_month paystart_year payissue
441       payip
442
443       referral_custnum comments
444     )
445
446   } );
447
448   my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
449   if ( $conf->exists('agent_ship_address') && $agent->agent_custnum ) {
450     my $agent_cust_main = $agent->agent_cust_main;
451     my $prefix = length($agent_cust_main->ship_last) ? 'ship_' : '';
452     $cust_main->set("ship_$_", $agent_cust_main->get("$prefix$_") )
453       foreach qw( address1 city county state zip country );
454
455     $cust_main->set("ship_$_", $cust_main->get($_))
456       foreach qw( last first );
457
458   }
459
460
461   return { 'error' => "Illegal payment type" }
462     unless grep { $_ eq $packet->{'payby'} }
463                 $conf->config('signup_server-payby');
464
465   if (FS::payby->realtime($packet->{payby})) {
466     my $payby = $packet->{payby};
467
468     my $agent = qsearchs('agent', { 'agentnum' => $agentnum });
469     return { 'error' => "Unknown reseller" }
470       unless $agent;
471
472     my $payment_gateway =
473       $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby) );
474
475     if ($payment_gateway->gateway_namespace eq
476         'Business::OnlineThirdPartyPayment'
477        ) {
478       $cust_main->payby('BILL');   # MCRD better?
479     }
480   }
481
482   $cust_main->payinfo($cust_main->daytime)
483     if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
484
485   my @invoicing_list = $packet->{'invoicing_list'}
486                          ? split( /\s*\,\s*/, $packet->{'invoicing_list'} )
487                          : ();
488
489   $packet->{'pkgpart'} =~ /^(\d+)$/ or '' =~ /^()$/;
490   my $pkgpart = $1;
491   return { 'error' => 'Please select a package' } unless $pkgpart; #msgcat
492
493   my $part_pkg =
494     qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
495       or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" };
496   my $svcpart = $part_pkg->svcpart($svc_x);
497
498   my $reg_code = '';
499   if ( $packet->{'reg_code'} ) {
500     $reg_code = qsearchs( 'reg_code', { 'code'     => $packet->{'reg_code'},
501                                         'agentnum' => $agentnum,             } )
502       or return { 'error' => 'Unknown registration code' };
503   }
504
505   my $cust_pkg = new FS::cust_pkg ( {
506     #later#'custnum' => $custnum,
507     'pkgpart'    => $packet->{'pkgpart'},
508     'promo_code' => $packet->{'promo_code'},
509     'reg_code'   => $packet->{'reg_code'},
510   } );
511   #my $error = $cust_pkg->check;
512   #return { 'error' => $error } if $error;
513
514   #should be all auto-magic and shit
515   my $svc;
516   if ( $svc_x eq 'svc_acct' ) {
517
518     $svc = new FS::svc_acct ( {
519       'svcpart'   => $svcpart,
520       map { $_ => $packet->{$_} }
521         qw( username _password sec_phrase popnum ),
522     } );
523
524     my @acct_snarf;
525     my $snarfnum = 1;
526     while (    exists($packet->{"snarf_machine$snarfnum"})
527             && length($packet->{"snarf_machine$snarfnum"}) ) {
528       my $acct_snarf = new FS::acct_snarf ( {
529         'machine'   => $packet->{"snarf_machine$snarfnum"},
530         'protocol'  => $packet->{"snarf_protocol$snarfnum"},
531         'username'  => $packet->{"snarf_username$snarfnum"},
532         '_password' => $packet->{"snarf_password$snarfnum"},
533       } );
534       $snarfnum++;
535       push @acct_snarf, $acct_snarf;
536     }
537     $svc->child_objects( \@acct_snarf );
538
539   } elsif ( $svc_x eq 'svc_phone' ) {
540
541     $svc = new FS::svc_phone ( {
542       'svcpart' => $svcpart,
543        map { $_ => $packet->{$_} }
544          qw( countrycode phonenum sip_password pin ),
545     } );
546
547   } else {
548     die "unknown signup service $svc_x";
549   }
550
551   my $y = $svc->setdefault; # arguably should be in new method
552   return { 'error' => $y } if $y && !ref($y);
553
554   #$error = $svc->check;
555   #return { 'error' => $error } if $error;
556
557   #setup a job dependancy to delay provisioning
558   my $placeholder = new FS::queue ( {
559     'job'    => 'FS::ClientAPI::Signup::__placeholder',
560     'status' => 'locked',
561   } );
562   my $error = $placeholder->insert;
563   return { 'error' => $error } if $error;
564
565   use Tie::RefHash;
566   tie my %hash, 'Tie::RefHash';
567   %hash = ( $cust_pkg => [ $svc ] );
568   #msgcat
569   $error = $cust_main->insert(
570     \%hash,
571     \@invoicing_list,
572     'depend_jobnum' => $placeholder->jobnum,
573   );
574   if ( $error ) {
575     my $perror = $placeholder->delete;
576     $error .= " (Additionally, error removing placeholder: $perror)" if $perror;
577     return { 'error' => $error };
578   }
579
580   if ( $conf->exists('signup_server-realtime') ) {
581
582     #warn "[fs_signup_server] Billing customer...\n" if $Debug;
583
584     my $bill_error = $cust_main->bill;
585     #warn "[fs_signup_server] error billing new customer: $bill_error"
586     #  if $bill_error;
587
588     $bill_error = $cust_main->apply_payments_and_credits;
589     #warn "[fs_signup_server] error applying payments and credits for".
590     #     " new customer: $bill_error"
591     #  if $bill_error;
592
593     if ($cust_main->_new_bop_required()) {
594       $bill_error = $cust_main->realtime_collect(
595          method        => FS::payby->payby2bop( $packet->{payby} ),
596          depend_jobnum => $placeholder->jobnum,
597       );
598     } else {
599       $bill_error = $cust_main->collect('realtime' => 1);
600     }
601     #warn "[fs_signup_server] error collecting from new customer: $bill_error"
602     #  if $bill_error;
603
604     if ($bill_error && ref($bill_error) eq 'HASH') {
605       return { 'error' => '_collect',
606                ( map { $_ => $bill_error->{$_} }
607                  qw(popup_url reference collectitems)
608                ),
609                amount => $cust_main->balance,
610              };
611     }
612
613     if ( $cust_main->balance > 0 ) {
614
615       #this makes sense.  credit is "un-doing" the invoice
616       $cust_main->credit( $cust_main->balance, 'signup server decline',
617                           'reason_type' => $conf->config('signup_credit_type'),
618                         );
619       $cust_main->apply_credits;
620
621       #should check list for errors...
622       #$cust_main->suspend;
623       local $FS::svc_Common::noexport_hack = 1;
624       $cust_main->cancel('quiet'=>1);
625
626       my $perror = $placeholder->depended_delete;
627       warn "error removing provisioning jobs after decline: $perror" if $perror;
628       unless ( $perror ) {
629         $perror = $placeholder->delete;
630         warn "error removing placeholder after decline: $perror" if $perror;
631       }
632
633       return { 'error' => '_decline' };
634     }
635
636   }
637
638   if ( $reg_code ) {
639     $error = $reg_code->delete;
640     return { 'error' => $error } if $error;
641   }
642
643   $error = $placeholder->delete;
644   return { 'error' => $error } if $error;
645
646   my %return = ( 'error'          => '',
647                  'signup_service' => $svc_x,
648                );
649
650   if ( $svc_x eq 'svc_acct' ) {
651     $return{$_} = $svc->$_() for qw( username _password );
652   } elsif ( $svc_x eq 'svc_phone' ) {
653     $return{$_} = $svc->$_() for qw( countrycode phonenum sip_password pin );
654   } else {
655     die "unknown signup service $svc_x";
656   }
657
658   return \%return;
659
660 }
661
662 sub capture_payment {
663   my $packet = shift;
664
665   warn "$me capture_payment called on $packet\n" if $DEBUG;
666
667   ###
668   # identify processor/gateway from called back URL
669   ###
670
671   my $conf = new FS::Conf;
672
673   my $url = $packet->{url};
674   my $payment_gateway =
675     qsearchs('payment_gateway', { 'gateway_callback_url' => popurl(0, $url) } );
676
677   unless ($payment_gateway) {
678
679     my ( $processor, $login, $password, $action, @bop_options ) =
680       $conf->config('business-onlinepayment');
681     $action ||= 'normal authorization';
682     pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
683     die "No real-time processor is enabled - ".
684         "did you set the business-onlinepayment configuration value?\n"
685       unless $processor;
686
687     $payment_gateway = new FS::payment_gateway( {
688       gateway_namespace => $conf->config('business-onlinepayment-namespace'),
689       gateway_module    => $processor,
690       gateway_username  => $login,
691       gateway_password  => $password,
692       gateway_action    => $action,
693       options   => [ ( @bop_options ) ],
694     });
695
696   }
697  
698   die "No real-time third party processor is enabled - ".
699       "did you set the business-onlinepayment configuration value?\n*"
700     unless $payment_gateway->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
701
702   ###
703   # locate pending transaction
704   ###
705
706   eval "use Business::OnlineThirdPartyPayment";
707   die $@ if $@;
708
709   my $transaction =
710     new Business::OnlineThirdPartyPayment( $payment_gateway->gateway_module,
711                                            @{ [ $payment_gateway->options ] },
712                                          );
713
714   my $paypendingnum = $transaction->reference($packet->{data});
715
716   my $cust_pay_pending =
717     qsearchs('cust_pay_pending', { paypendingnum => $paypendingnum } );
718
719   unless ($cust_pay_pending) {
720     my $bill_error = "No payment is being processed with id $paypendingnum".
721                      "; Transaction aborted.";
722     return { error => '_decline', bill_error => $bill_error };
723   }
724
725   if ($cust_pay_pending->status ne 'pending') {
726     my $bill_error = "Payment with id $paypendingnum is not pending, but ".
727                      $cust_pay_pending->status.  "; Transaction aborted.";
728     return { error => '_decline', bill_error => $bill_error };
729   }
730
731   my $cust_main = $cust_pay_pending->cust_main;
732   my $bill_error =
733     $cust_main->realtime_botpp_capture( $cust_pay_pending, %{$packet->{data}} );
734
735   return { 'error'      => ( $bill_error->{bill_error} ? '_decline' : '' ),
736            %$bill_error,
737          };
738
739 }
740
741 1;