+ do_process_payment($payment_info);
+
+}
+
+sub process_payment {
+ my $p = shift;
+
+ my $payment_info = validate_payment($p);
+ return $payment_info if $payment_info->{'error'};
+
+ do_process_payment($payment_info);
+
+}
+
+sub do_process_payment {
+ my $validate = shift;
+
+ my $cust_main = $validate->{'cust_main'};
+
+ my $amount = delete $validate->{'amount'};
+ my $paynum = '';
+
+ my $payby = delete $validate->{'payby'};
+
+ my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount,
+ 'quiet' => 1,
+ 'selfservice' => 1,
+ 'paynum_ref' => \$paynum,
+ %$validate,
+ );
+ return { 'error' => $error } if $error;
+
+ $cust_main->apply_payments;
+
+ if ( $validate->{'save'} ) {
+ my $new = new FS::cust_main { $cust_main->hash };
+ if ($payby eq 'CARD' || $payby eq 'DCRD') {
+ $new->set( $_ => $validate->{$_} )
+ foreach qw( payname paystart_month paystart_year payissue payip
+ address1 address2 city state zip country );
+ $new->set( 'payby' => $validate->{'auto'} ? 'CARD' : 'DCRD' );
+ } elsif ($payby eq 'CHEK' || $payby eq 'DCHK') {
+ $new->set( $_ => $validate->{$_} )
+ foreach qw( payname payip paytype paystate
+ stateid stateid_state );
+ $new->set( 'payby' => $validate->{'auto'} ? 'CHEK' : 'DCHK' );
+ }
+ $new->set( 'payinfo' => $cust_main->card_token || $validate->{'payinfo'} );
+ $new->set( 'paydate' => $validate->{'paydate'} );
+ my $error = $new->replace($cust_main);
+ if ( $error ) {
+ #no, this causes customers to process their payments again
+ #return { 'error' => $error };
+ #XXX just warn verosely for now so i can figure out how these happen in
+ # the first place, eventually should redirect them to the "change
+ #address" page but indicate the payment did process??
+ delete($validate->{'payinfo'}); #don't want to log this!
+ warn "WARNING: error changing customer info when processing payment (not returning to customer as a processing error): $error\n".
+ "NEW: ". Dumper($new)."\n".
+ "OLD: ". Dumper($cust_main)."\n".
+ "PACKET: ". Dumper($validate)."\n";
+ #} else {
+ #not needed...
+ #$cust_main = $new;
+ }
+ }
+
+ my $cust_pay = '';
+ my $receipt_html = '';
+ if ($paynum) {
+ # currently supported for realtime CC only; send receipt data to SS
+ $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } );
+ if($cust_pay) {
+ $receipt_html = qq!
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=2>
+
+<TR>
+ <TD ALIGN="right">Payment#</TD>
+ <TD BGCOLOR="#FFFFFF"><B>! . $cust_pay->paynum . qq!</B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Date</TD>
+
+ <TD BGCOLOR="#FFFFFF"><B>! .
+ time2str("%a %b %o, %Y %r", $cust_pay->_date)
+ . qq!</B></TD>
+</TR>
+
+
+<TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#FFFFFF"><B>! . sprintf('%.2f', $cust_pay->paid) . qq!</B></TD>
+
+</TR>
+
+<TR>
+ <TD ALIGN="right">Payment method</TD>
+ <TD BGCOLOR="#FFFFFF"><B>! . $cust_pay->payby_name .' #'. $cust_pay->paymask
+ . qq!</B></TD>
+</TR>
+
+</TABLE>
+!;
+ }
+ }
+
+ if ( $cust_pay ) {
+
+ my($gw, $auth, $order) = split(':', $cust_pay->paybatch);
+
+ return {
+ 'error' => '',
+ 'amount' => sprintf('%.2f', $cust_pay->paid),
+ 'date' => $cust_pay->_date,
+ 'date_pretty' => time2str('%Y-%m-%d', $cust_pay->_date),
+ 'time_pretty' => time2str('%T', $cust_pay->_date),
+ 'auth_num' => $auth,
+ 'order_num' => $order,
+ 'receipt_html' => $receipt_html,
+ };
+
+ } else {
+
+ return {
+ 'error' => '',
+ 'receipt_html' => '',
+ };
+
+ }
+
+}
+
+sub realtime_collect {
+ my $p = shift;
+
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $amount;
+ if ( $p->{'amount'} ) {
+ $amount = $p->{'amount'};
+ }
+ elsif ( $session->{'pkgnum'} ) {
+ $amount = $cust_main->balance_pkgnum( $session->{'pkgnum'} );
+ }
+ else {
+ $amount = $cust_main->balance;
+ }
+
+ my $error = $cust_main->realtime_collect(
+ 'method' => $p->{'method'},
+ 'amount' => $amount,
+ 'pkgnum' => $session->{'pkgnum'},
+ 'session_id' => $p->{'session_id'},
+ 'apply' => 1,
+ 'selfservice'=> 1,
+ );
+ return { 'error' => $error } unless ref( $error );
+
+ return { 'error' => '', amount => $amount, %$error };
+}
+
+sub process_payment_order_pkg {
+ my $p = shift;
+
+ my $hr = process_payment($p);
+ return $hr if $hr->{'error'};
+
+ order_pkg($p);
+}
+
+sub process_payment_order_renew {
+ my $p = shift;
+
+ my $hr = process_payment($p);
+ return $hr if $hr->{'error'};
+
+ order_renew($p);
+}
+
+sub process_prepay {
+
+ my $p = shift;
+
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my %return;
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my( $amount, $seconds, $upbytes, $downbytes, $totalbytes ) = ( 0, 0, 0, 0, 0 );
+ my $error = $cust_main->recharge_prepay( $p->{'prepaid_cardnum'},
+ \$amount,
+ \$seconds,
+ \$upbytes,
+ \$downbytes,
+ \$totalbytes,
+ );
+
+ return { 'error' => $error } if $error;
+
+ return { 'error' => '',
+ 'amount' => $amount,
+ 'seconds' => $seconds,
+ 'duration' => duration_exact($seconds),
+ 'upbytes' => $upbytes,
+ 'upload' => FS::UI::bytecount::bytecount_unexact($upbytes),
+ 'downbytes' => $downbytes,
+ 'download' => FS::UI::bytecount::bytecount_unexact($downbytes),
+ 'totalbytes'=> $totalbytes,
+ 'totalload' => FS::UI::bytecount::bytecount_unexact($totalbytes),
+ };
+
+}
+
+sub invoice {
+ my $p = shift;
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $invnum = $p->{'invnum'};
+
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum,
+ 'custnum' => $custnum } )
+ or return { 'error' => "Can't find invnum" };
+
+ #my %return;
+
+ return { 'error' => '',
+ 'invnum' => $invnum,
+ 'invoice_text' => join('', $cust_bill->print_text ),
+ 'invoice_html' => $cust_bill->print_html( { unsquelch_cdr => 1 } ),
+ };
+
+}
+
+sub invoice_pdf {
+ my $p = shift;
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $invnum = $p->{'invnum'};
+
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum,
+ 'custnum' => $custnum } )
+ or return { 'error' => "Can't find invnum" };
+
+ #my %return;
+
+ return { 'error' => '',
+ 'invnum' => $invnum,
+ 'invoice_pdf' => $cust_bill->print_pdf({
+ 'unsquelch_cdr' => 1,
+ 'locale' => $p->{'locale'},
+ }),
+ };
+
+}
+
+sub legacy_invoice {
+ my $p = shift;
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $legacyinvnum = $p->{'legacyinvnum'};
+
+ my %hash = (
+ 'legacyinvnum' => $legacyinvnum,
+ 'custnum' => $custnum,
+ );
+
+ my $legacy_cust_bill =
+ qsearchs('legacy_cust_bill', { %hash, 'locale' => $p->{'locale'} } )
+ || qsearchs('legacy_cust_bill', \%hash )
+ or return { 'error' => "Can't find legacyinvnum" };
+
+ #my %return;
+
+ return { 'error' => '',
+ 'legacyinvnum' => $legacyinvnum,
+ 'legacyid' => $legacy_cust_bill->legacyid,
+ 'invoice_html' => $legacy_cust_bill->content_html,
+ };
+
+}
+
+sub legacy_invoice_pdf {
+ my $p = shift;
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $legacyinvnum = $p->{'legacyinvnum'};
+
+ my $legacy_cust_bill = qsearchs('legacy_cust_bill', {
+ 'legacyinvnum' => $legacyinvnum,
+ 'custnum' => $custnum,
+ }) or return { 'error' => "Can't find legacyinvnum" };
+
+ #my %return;
+
+ return { 'error' => '',
+ 'legacyinvnum' => $legacyinvnum,
+ 'legacyid' => $legacy_cust_bill->legacyid,
+ 'invoice_pdf' => $legacy_cust_bill->content_pdf,
+ };
+
+}
+
+sub invoice_logo {
+ my $p = shift;
+
+ #sessioning for this? how do we get the session id to the backend invoice
+ # template so it can add it to the link, blah
+
+ my $agentnum = '';
+ if ( $p->{'invnum'} ) {
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $p->{'invnum'} } )
+ or return { 'error' => 'unknown invnum' };
+ $agentnum = $cust_bill->cust_main->agentnum;
+ }
+
+ my $templatename = $p->{'template'} || $p->{'templatename'};
+
+ #false laziness-ish w/view/cust_bill-logo.cgi
+
+ my $conf = new FS::Conf;
+ if ( $templatename =~ /^([^\.\/]*)$/ && $conf->exists("logo_$1.png") ) {
+ $templatename = "_$1";
+ } else {
+ $templatename = '';
+ }
+
+ my $filename = "logo$templatename.png";
+
+ return { 'error' => '',
+ 'logo' => $conf->config_binary($filename, $agentnum),
+ 'content_type' => 'image/png', #should allow gif, jpg too
+ };
+}
+
+
+sub list_invoices {
+ my $p = shift;
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $conf = new FS::Conf;
+
+ my @legacy_cust_bill = $cust_main->legacy_cust_bill;
+
+ my @cust_bill = grep ! $_->hide, $cust_main->cust_bill;
+
+ my $balance = 0;