8 % if ( $payby{'BILL'} && $curuser->access_right(['Post payment', 'Post check payment' ]) ) {
9 <% $s++ ? ' | ' : '' %>
10 <& /elements/popup_link-cust_main.html,
11 'label' => emt('Enter check payment'),
12 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=BILL",
13 'cust_main' => $cust_main,
14 'actionlabel' => emt('Enter check payment'),
15 'width' => ( $opt{'pkg-balances'} ? 763 : 392),
20 % if ( $payby{'CASH'} && $curuser->access_right(['Post payment', 'Post cash payment']) ) {
21 <% $s++ ? ' | ' : '' %>
22 <& /elements/popup_link-cust_main.html,
23 'label' => emt('Enter cash payment'),
24 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=CASH",
25 'cust_main' => $cust_main,
26 'actionlabel' => emt('Enter cash payment'),
27 'width' => ( $opt{'pkg-balances'} ? 763 : 392),
32 % if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) {
33 <% $s++ ? ' | ' : '' %>
34 <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;custnum=<% $custnum %>"><% mt('Enter Western Union payment') |h %></A>
37 <% $s ? '<BR>' : '' %>
40 % if ( ( $payby{'CARD'} || $payby{'DCRD'} )
41 % && $curuser->access_right(['Process payment', 'Process credit card payment'])
42 % && ! $cust_main->is_encrypted($cust_main->payinfo)
44 <% $s++ ? ' | ' : '' %>
45 <A HREF="<% $p %>misc/payment.cgi?payby=CARD;custnum=<% $custnum %>"><% mt('Process credit card payment') |h %></A>
48 % if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
49 % && $curuser->access_right(['Process payment', 'Process Echeck payment'])
50 % && ! $cust_main->is_encrypted($cust_main->payinfo)
52 <% $s++ ? ' | ' : '' %>
53 <A HREF="<% $p %>misc/payment.cgi?payby=CHEK;custnum=<% $custnum %>"><% mt('Process electronic check (ACH) payment') |h %></A>
56 % if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) {
57 <% $s++ ? ' | ' : '' %>
58 <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<% $custnum %>"><% mt('Post manual (offline/POS) credit card payment') |h %></A>
61 % if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) {
62 <% $s++ ? ' | ' : '' %>
63 <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCHK;custnum=<% $custnum %>"><% mt('Post manual (offline) electronic check payment') |h %></A>
66 <% $s ? '<BR>' : '' %>
71 % if ( $curuser->access_right('Post credit') ) {
72 <% $s++ ? ' | ' : '' %>
73 <& /elements/popup_link-cust_main.html,
74 'label' => emt('Enter credit'),
75 'action' => "${p}edit/cust_credit.cgi",
76 'cust_main' => $cust_main,
77 'actionlabel' => emt('Enter credit'),
78 'width' => ( $opt{'pkg-balances'} ? 763 : 616),
81 % if ( $curuser->access_right('Credit line items') ) {
82 <% $s++ ? ' | ' : '' %>
83 <& /elements/popup_link-cust_main.html,
84 'label' => emt('Credit line items'),
85 #'action' => "${p}search/cust_bill_pkg.cgi?nottax=1;type=select",
86 'action' => "${p}edit/credit-cust_bill_pkg.html",
87 'cust_main' => $cust_main,
88 'actionlabel' => emt('Credit line items'),
93 <% $s ? '<BR>' : '' %>
98 % if ( $payby{'BILL'} && $curuser->access_right(['Post refund', 'Post check refund']) ) {
99 <% $s++ ? ' | ' : '' %>
100 <& /elements/popup_link-cust_main.html,
101 'label' => emt('Enter check refund'),
102 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=BILL",
103 'cust_main' => $cust_main,
104 'actionlabel' => emt('Enter check refund'),
109 % if ( $payby{'CASH'} && $curuser->access_right(['Post refund', 'Post cash refund']) ) {
110 <% $s++ ? ' | ' : '' %>
111 <& /elements/popup_link-cust_main.html,
112 'label' => emt('Enter cash refund'),
113 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=CASH",
114 'cust_main' => $cust_main,
115 'actionlabel' => emt('Enter cash refund'),
120 %# someday, perhaps. very few gateways let you do unlinked refunds at all.
121 %# Authorize.net makes you sign a special form
123 %# % if ( ( $payby{'CARD'} || $payby{'DCRD'} )
124 %# % && $curuser->access_right('Process refund')
125 %# % && ! $cust_main->is_encrypted($cust_main->payinfo)
127 %# <% $s++ ? ' | ' : '' %>
128 %# <A HREF="<% $p %>misc/refund.cgi?payby=CARD;custnum=<% $custnum %>">Process credit card refund</A>
131 %# % if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
132 %# % && $curuser->access_right('Process refund')
133 %# % && ! $cust_main->is_encrypted($cust_main->payinfo)
135 %# <% $s++ ? ' | ' : '' %>
136 %# <A HREF="<% $p %>misc/refund.cgi?payby=CHEK;custnum=<% $custnum %>">Process electronic check (ACH) refund</A>
139 % if ( $payby{'MCRD'} && $curuser->access_right('Post refund') ) {
140 <% $s++ ? ' | ' : '' %>
141 <A HREF="<% $p %>edit/cust_refund.cgi?payby=MCRD;custnum=<% $custnum %>"><% mt('Post manual (offline/POS) credit card refund') |h %></A>
144 % if ( $payby{'MCHK'} && $curuser->access_right('Post refund') ) {
145 <% $s++ ? ' | ' : '' %>
146 <A HREF="<% $p %>edit/cust_refund.cgi?payby=MCRD;custnum=<% $custnum %>"><% mt('Post manual (offline) electronic check refund') |h %></A>
150 <TD ALIGN="right" VALIGN="top">
152 %# invoice reports, combined statement
153 % if ( $curuser->access_right('List invoices') ) {
154 % if ( $num_cust_bill > 0 ) {
155 <A HREF="<% $p %>view/cust_main_statement-pdf.cgi?<% $custnum %>"><%
156 mt('Download typeset statement PDF') |h %></A>
159 <A HREF="<% $p %>search/report_cust_bill.html?custnum=<% $custnum %>"><% mt('Invoice reports') |h %></A>
163 %# XXX payments, credits, refund reports
165 %# tax exemption link
167 % my $view_exemptions = $curuser->access_right('View customer tax exemptions');
168 % my $add_adjustment = ( $conf->exists('enable_tax_adjustments')
169 % && $curuser->access_right('Add customer tax adjustment')
171 % if ( $view_exemptions || $add_adjustment ) {
173 % if ( $view_exemptions ) {
174 <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>"><% mt('View tax exemptions') |h %></A>
175 <% $add_adjustment ? '|' : '' %>
178 % if ( $add_adjustment ) {
179 <& /elements/popup_link.html, {
180 'action' => $p.'edit/cust_tax_adjustment.html?custnum='. $cust_main->custnum,
181 'label' => emt('Add tax adjustment'),
182 'actionlabel' => emt('Add tax adjustment'),
187 <A HREF="<% $p %>search/cust_tax_adjustment.html?custnum=<% $custnum %>"><% mt('View tax adjustments') |h %></A>
193 %# batched payment links
195 % if ( ( $conf->exists('batch-enable') || $conf->config('batch-enable_payby') )
196 % && $curuser->access_right('View customer batched payments')
199 <% mt('View batched payments:') |h %>
200 % foreach my $status (qw( Queued In-transit Complete All )) {
201 <A HREF="<% $p %>search/cust_pay_batch.cgi?status=<% $status{$status} %>;custnum=<% $custnum %>"><% mt($status) |h %></A>
202 <% $status ne 'All' ? '|' : '' %>
207 %# pending payment links
209 % if ( $curuser->access_right('View customer pending payments')
210 % && scalar($cust_main->cust_pay_pending)
213 <A HREF="<% $p %>search/cust_pay_pending.html?magic=_date;statusNOT=done;custnum=<% $custnum %>"><% mt('View pending payments') |h %></A><BR>
223 <& /elements/table-grid.html &>
224 % my $bgcolor1 = '#eeeeee';
225 % my $bgcolor2 = '#ffffff';
229 <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Date') |h %></TH>
230 <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Description') |h %></TH>
231 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Invoice') |h %></FONT></TH>
232 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Payment') |h %></FONT></TH>
233 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('In-house Credit') |h %></FONT></TH>
234 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Refund') |h %></FONT></TH>
235 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Balance') |h %></FONT></TH>
238 %#display payment history
244 %my $old_history = 0;
247 %foreach my $item ( @history ) {
249 % $lastdate = $item->{'date'};
252 % if ( $item->{'hide'} ) {
253 % $display = ' STYLE="display:none" ';
256 % if ( $bgcolor eq $bgcolor1 ) {
257 % $bgcolor = $bgcolor2;
259 % $bgcolor = $bgcolor1;
262 % my $charge = exists($item->{'charge'})
263 % ? sprintf("$money_char\%.2f", $item->{'charge'})
264 % : exists($item->{'charge_nobal'})
265 % ? sprintf("$money_char\%.2f", $item->{'charge_nobal'})
266 % : exists($item->{'void_charge'})
267 % ? sprintf("<DEL>$money_char\%.2f</DEL>", $item->{'void_charge'})
270 % my $payment = exists($item->{'payment'})
271 % ? sprintf("- $money_char\%.2f", $item->{'payment'})
274 % $payment ||= sprintf( "<DEL>- $money_char\%.2f</DEL>",
275 % $item->{'void_payment'}
277 % if exists($item->{'void_payment'});
279 % my $credit = exists($item->{'credit'})
280 % ? sprintf("- $money_char\%.2f", $item->{'credit'})
283 % $credit ||= sprintf( "<DEL>- $money_char\%.2f</DEL>",
284 % $item->{'void_credit'}
286 % if exists($item->{'void_credit'});
288 % my $refund = exists($item->{'refund'})
289 % ? sprintf("$money_char\%.2f", $item->{'refund'})
292 % my $target = exists($item->{'target'}) ? $item->{'target'} : '';
294 % my $showbalance = $money_char . $item->{'balance'};
295 % $showbalance =~ s/^\$\-/- \$/;
297 <TR <% $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>>
298 <TD VALIGN="top" CLASS="grid" BGCOLOR="<% $bgcolor %>">
299 % unless ( !$target || $target{$target}++ ) {
301 <A NAME="<% $target %>">
304 <% time2str($date_format, $item->{'date'}) %>
305 % if ( $target && $target{$target} == 1 ) {
312 <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
313 <% $item->{'desc'} %>
315 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
318 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
321 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
324 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
327 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
332 % if ( $item->{'balance_forward'} ) {
333 <& .balance_forward_row, $item->{'balance'}, $item->{'date'} &>
342 <SCRIPT TYPE="text/javascript">
344 function show_history () {
345 //alert('showing history!');
347 var balance_forward_row = document.getElementById('balance_forward_row');
349 balance_forward_row.style.display = 'none';
350 for ( var i = 0; i < <% $old_history %>; i++ ) {
351 var oldRow = document.getElementById('old_history'+i);
352 oldRow.style.display = '';
358 <%def .balance_forward_row>
359 % my( $b, $date ) = @_;
360 % ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/- \$/;
362 <TR ID="balance_forward_row">
363 <TD CLASS="grid" BGCOLOR="#dddddd">
364 <% time2str($date_format, $date) %>
367 <TD CLASS="grid" BGCOLOR="#dddddd">
368 <I><% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %></I>
369 (<A HREF="javascript:void(0);" onClick="show_history();"><% mt('show prior history') |h %></A>)
372 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
373 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
374 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
375 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
376 <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
381 my $conf = new FS::Conf;
382 my $date_format = $conf->config('date_format') || '%m/%d/%Y';
383 my $money_char = $conf->config('money_char') || '$';
387 my( $cust_main ) = @_;
388 my $custnum = $cust_main->custnum;
390 my $curuser = $FS::CurrentUser::CurrentUser;
392 my @payby = grep /\w/, $conf->config('payby');
393 #@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
394 @payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
396 my %payby = map { $_=>1 } @payby;
399 'Queued' => 'O', #Open
401 'Complete' => 'R', #Resolved
411 ( map { $_ => scalar($conf->config($_)) }
412 qw( card_refund-days date_format )
414 ( map { $_ => $conf->exists($_) }
415 qw( deleteinvoices deletepayments deleterefunds pkg-balances
416 cust_credit_bill_pkg-manual cust_bill_pay_pkg-manual
419 'money_char ' => $money_char,
422 ( map { $_ => $curuser->access_right($_) }
424 'View invoices', 'Void invoices', 'Unvoid invoices', 'Delete invoices',
425 'Apply payment', 'Refund credit card payment', 'Refund Echeck payment',
426 'Credit card void', 'Echeck void', 'Void payments', 'Unvoid payments',
427 'Delete payment', 'Unapply payment',
428 'Apply credit', 'Delete credit', 'Unapply credit',
430 'Billing event reports', 'View customer billing events',
434 #customer information
435 'total_owed' => $cust_main->total_owed,
436 'total_unapplied_refunds' => $cust_main->total_unapplied_refunds,
439 $opt{'date_format'} ||= '%m/%d/%Y';
442 foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) {
444 'date' => $legacy_cust_bill->_date,
446 'num' => $legacy_cust_bill->legacyid,
447 'desc' => include('payment_history/legacy_invoice.html', $legacy_cust_bill, %opt ),
448 'charge_nobal' => $legacy_cust_bill->charged,
453 my $num_cust_bill = 0;
454 foreach my $cust_bill ($cust_main->cust_bill) {
456 'date' => $cust_bill->_date,
458 'num' => $cust_bill->invnum,
459 'desc' => include('payment_history/invoice.html', $cust_bill, %opt ),
460 'charge' => $cust_bill->charged,
466 foreach my $cust_bill_void ($cust_main->cust_bill_void) {
468 'date' => $cust_bill_void->_date,
470 'num' => $cust_bill_void->invnum,
471 'desc' => include('payment_history/voided_invoice.html', $cust_bill_void, %opt ),
472 'void_charge' => $cust_bill_void->charged,
477 foreach my $cust_statement ($cust_main->cust_statement) {
479 'date' => $cust_statement->_date,
481 'num' => $cust_statement->statementnum,
482 'desc' => include('payment_history/statement.html', $cust_statement, %opt ),
483 #'charge' => $cust_bill->charged,
487 #payments (some false laziness w/credits)
488 foreach my $cust_pay ($cust_main->cust_pay) {
490 'date' => $cust_pay->_date,
492 'num' => $cust_pay->paynum,
493 'desc' => include('payment_history/payment.html', $cust_pay, %opt ),
494 'payment' => $cust_pay->paid,
495 #'target' => $target, #XXX
500 foreach my $cust_pay_pending ($cust_main->cust_pay_pending) {
502 'date' => $cust_pay_pending->_date,
504 'num' => $cust_pay_pending->paypendingnum,
505 'desc' => include('payment_history/pending_payment.html', $cust_pay_pending, %opt ),
506 'void_payment' => $cust_pay_pending->paid,
512 foreach my $cust_pay_void ($cust_main->cust_pay_void) {
514 'date' => $cust_pay_void->_date,
516 'num' => $cust_pay_void->paynum,
517 'desc' => include('payment_history/voided_payment.html', $cust_pay_void, %opt ),
518 'void_payment' => $cust_pay_void->paid,
524 foreach my $cust_credit_void ($cust_main->cust_credit_void) {
526 'date' => $cust_credit_void->_date,
528 'num' => $cust_credit_void->paynum,
529 'desc' => include('payment_history/voided_credit.html', $cust_credit_void, %opt ),
530 'void_credit' => $cust_credit_void->amount,
535 foreach my $cust_pay_pending ($cust_main->cust_pay_pending_attempt) {
537 'date' => $cust_pay_pending->_date,
539 'num' => $cust_pay_pending->paypendingnum,
540 'desc' => include('payment_history/attempted_payment.html', $cust_pay_pending, %opt ),
541 'void_payment' => $cust_pay_pending->paid, #??
542 #'target' => $target, #XXX
545 #declined batch payments
546 foreach my $cust_pay_batch (
547 $cust_main->cust_pay_batch(hashref => {status => 'Declined'})
549 my $pay_batch = $cust_pay_batch->pay_batch;
551 'date' => $pay_batch->upload,
553 'num' => $cust_pay_batch->paybatchnum,
554 'desc' => include('payment_history/attempted_batch_payment.html', $cust_pay_batch, %opt),
555 'void_payment' => $cust_pay_batch->amount,
559 #credits (some false laziness w/payments)
560 foreach my $cust_credit ($cust_main->cust_credit) {
562 'date' => $cust_credit->_date,
564 'num' => $cust_credit->crednum,
565 'desc' => include('payment_history/credit.html', $cust_credit, %opt ),
566 'credit' => $cust_credit->amount,
572 foreach my $cust_refund ($cust_main->cust_refund) {
574 'date' => $cust_refund->_date,
576 'num' => $cust_refund->refundnum,
577 'desc' => include('payment_history/refund.html', $cust_refund, %opt),
578 'refund' => $cust_refund->refund,
583 # sort in forward order first, and calculate running balances
584 my $years = $conf->config('payment_history-years') || 2;
585 my $older_than = time - $years * 31556926; #60*60*24*365.2422
588 @history = sort { $a->{date} <=> $b->{date}
589 or $a->{order} <=> $b->{order}
590 or $a->{num} <=> $b->{num}
596 foreach my $item (@history) {
597 $balance += $item->{'charge'} if exists $item->{'charge'};
598 $balance -= $item->{'payment'} if exists $item->{'payment'};
599 $balance -= $item->{'credit'} if exists $item->{'credit'};
600 $balance += $item->{'refund'} if exists $item->{'refund'};
601 $balance = sprintf("%.2f", $balance);
602 $balance =~ s/^\-0\.00$/0.00/;
603 $item->{'balance'} = $balance;
605 if ( $item->{'date'} < $older_than ) {
607 } elsif ( $history[$i-1]->{'hide'} ) {
608 # this is the end of the hidden section
609 $history[$i-1]->{'balance_forward'} = 1;
613 if ( @history and $history[-1]->{'hide'} ) {
614 # then everything is hidden
615 $history[-1]->{'balance_forward'} = 1;
618 # then sort in user-pref order
619 if ( $curuser->option('history_order') eq 'newest' ) {
620 @history = sort { $b->{date} <=> $a->{date}
621 or $b->{order} <=> $a->{order} #or still forward here?
622 or $b->{num} <=> $a->{num}
625 } # else it's already oldest-first, and there are no other options yet
627 sub translate_payby {
628 my ($payby,$payinfo) = (shift,shift);
630 FS::payby->payby2shortname,
631 BILL => $payinfo ? emt('Check #') : '',
632 CHEK => emt('Electronic check '),
633 PREP => emt('Prepaid card '),
634 CARD => emt('Credit card #'),
635 COMP => emt('Complimentary by '),
636 #CASH => emt('Cash'),
637 #WEST => emt('Western Union'),
638 #MCRD => emt('Manual credit card'),
640 $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby;
644 sub translate_payby_refund {
645 my ($payby,$payinfo) = (shift,shift);
647 FS::payby->payby2shortname,
648 BILL => $payinfo ? emt('Check #') : emt('Check'),
649 CHEK => emt('Electronic check '),
650 CARD => emt('Credit card #'),
651 COMP => emt('Complimentary by '),
653 $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby;
657 sub translate_payinfo {
659 my $payby = $object->payby;
660 my $payinfo = $object->payinfo;
662 if ( $payby eq 'CARD' ) {
663 $payinfo = $object->paymask;
664 } elsif ( $payby eq 'CHEK' ) {
665 #false laziness w/payinfo_Mixin::payby_payinfo_pretty, should use that
666 my( $account, $aba ) = split('@', $object->paymask );
667 if ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #blame canada
668 my($branch, $routing) = ($1, $2);
669 $payinfo = emt("Routing [_1], Branch [_2], Acct [_3]",
670 $routing, $branch, $account);
672 $payinfo = emt("Routing [_1], Acct [_2]", $aba, $account);
679 sub areyousure_link {
680 my ($url,$msg,$title,$label) = (shift,shift,shift,shift);
681 ' (<A HREF="javascript:areyousure(\''.$url.'\',\''.$msg.'\')" TITLE="'.$title.'">'.$label.'</A>)';