voiding of echeck payments instead of refunds
[freeside.git] / httemplate / view / cust_main / payment_history.html
1 <%
2   my( $cust_main ) = @_;
3   my $conf = new FS::Conf;
4   my $custnum = $cust_main->custnum;
5 %>
6
7 <BR><BR><A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
8 <A HREF="<%= $p %>edit/cust_pay.cgi?custnum=<%= $custnum %>">Post cash/check payment</A>
9 | <A HREF="<%= $p %>misc/payment.cgi?payby=CARD;custnum=<%= $custnum %>">Process credit card payment</A>
10 | <A HREF="<%= $p %>misc/payment.cgi?payby=CHEK;custnum=<%= $custnum %>">Process electronic check (ACH) payment</A>
11 <BR><A HREF="<%= $p %>edit/cust_credit.cgi?<%= $custnum %>">Post credit</A>
12 <BR>
13
14 <%
15 #get payment history
16 my @history = ();
17
18 #invoices
19 foreach my $cust_bill ($cust_main->cust_bill) {
20   my $pre = ( $cust_bill->owed > 0 )
21               ? '<B><FONT SIZE="+1" COLOR="#FF0000">Open '
22               : '';
23   my $post = ( $cust_bill->owed > 0 ) ? '</FONT></B>' : '';
24   my $invnum = $cust_bill->invnum;
25   push @history, {
26     'date'   => $cust_bill->_date,
27     'desc'   => qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!. $pre.
28                 "Invoice #$invnum (Balance \$". $cust_bill->owed. ')'.
29                 $post. '</A>',
30     'charge' => $cust_bill->charged,
31   };
32 }
33
34 #payments (some false laziness w/credits)
35 foreach my $cust_pay ($cust_main->cust_pay) {
36
37   my $payby = $cust_pay->payby;
38
39   my $payinfo;
40   if ( $payby eq 'CARD' ) {
41     $payinfo = $cust_pay->payinfo_masked;
42   } elsif ( $payby eq 'CHEK' && $cust_pay->payinfo =~ /^(\d+)\@(\d+)$/ ) {
43     $payinfo = "ABA $2, Acct# $1";
44   } else {
45     $payinfo = $cust_pay->payinfo;
46   }
47   my @cust_bill_pay = $cust_pay->cust_bill_pay;
48   my @cust_pay_refund = $cust_pay->cust_pay_refund;
49
50   my $target = "$payby$payinfo";
51   $payby =~ s/^BILL$/Check #/ if $payinfo;
52   $payby =~ s/^CHEK$/Electronic check /;
53   $payby =~ s/^BILL$//;
54   $payby =~ s/^(CARD|COMP)$/$1 /;
55   my $info = $payby ? " ($payby$payinfo)" : '';
56
57   my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
58   if (    scalar(@cust_bill_pay)   == 0
59        && scalar(@cust_pay_refund) == 0 ) {
60     #completely unapplied
61     $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
62     $post = '</FONT></B>';
63     $apply = qq! (<A HREF="${p}edit/cust_bill_pay.cgi?!.
64              $cust_pay->paynum. '">apply</A>)';
65   } elsif (    scalar(@cust_bill_pay)   == 1
66             && scalar(@cust_pay_refund) == 0
67             && $cust_pay->unapplied == 0     ) {
68     #applied to one invoice, the usual situation
69     $desc = ' applied to Invoice #'. $cust_bill_pay[0]->invnum;
70   } elsif (    scalar(@cust_bill_pay)   == 0
71             && scalar(@cust_pay_refund) == 1
72             && $cust_pay->unapplied == 0     ) {
73     #applied to one refund
74     $desc = ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date);
75   } else {
76     #complicated
77     $desc = '<BR>';
78     foreach my $app ( sort { $a->_date <=> $b->_date }
79                            ( @cust_bill_pay, @cust_pay_refund ) ) {
80       if ( $app->isa('FS::cust_bill_pay') ) {
81         $desc .= '&nbsp;&nbsp;'.
82                  '$'. $app->amount.
83                  ' applied to Invoice #'. $app->invnum.
84                  '<BR>';
85                  #' on '. time2str("%D", $cust_bill_pay->_date).
86       } elsif ( $app->isa('FS::cust_pay_refund') ) {
87         $desc .= '&nbsp;&nbsp;'.
88                  '$'. $app->amount.
89                  ' refunded on'. time2str("%D", $app->_date).
90                  '<BR>';
91       } else {
92         die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund";
93       }
94     }
95     if ( $cust_pay->unapplied > 0 ) {
96       $desc .= '&nbsp;&nbsp;'.
97                '<B><FONT COLOR="#FF0000">$'.
98                $cust_pay->unapplied. ' unapplied</FONT></B>'.
99                qq! (<A HREF="${p}edit/cust_bill_pay.cgi?!.
100                $cust_pay->paynum. '">apply</A>)'.
101                '<BR>';
102     }
103   }
104
105   my $refund = '';
106   my $refund_days = $conf->config('card_refund-days') || 120;
107   if (    $cust_pay->closed !~ /^Y/i
108        && $cust_pay->payby =~ /^(CARD|CHEK)$/
109        && time-$cust_pay->_date < $refund_days*86400
110        && $cust_pay->unrefunded > 0
111   ) {
112     $refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!.
113               qq!paynum=!. $cust_pay->paynum. '"'.
114               qq! TITLE="Send a refund for this payment to the payment gateway"!.
115               qq!>refund</A>)!;
116   }
117
118   my $void = '';
119   if (    $cust_pay->closed !~ /^Y/i
120        && $cust_pay->payby ne 'CARD'
121        && ( $cust_pay->payby ne 'CHEK' || $conf->exists('echeck-void') ) 
122      ) {
123     $void = qq! (<A HREF="javascript:areyousure('!.
124             qq!${p}misc/void-cust_pay.cgi?!. $cust_pay->paynum.
125             qq!', 'Are you sure you want to void this payment?')"!.
126             qq! TITLE="Void this payment from the database!.
127               ( $cust_pay->payby eq 'CHEK'
128                 ? ' (do not send anything to the payment gateway)'
129                 : '' 
130               ). '"'.
131             qq!>void</A>)!;
132   }
133
134   my $delete = '';
135   if ( $cust_pay->closed !~ /^Y/i && $conf->exists('deletepayments') ) {
136     $delete = qq! (<A HREF="javascript:areyousure('!.
137               qq!${p}misc/delete-cust_pay.cgi?!. $cust_pay->paynum.
138               qq!', 'Are you sure you want to delete this payment?')"!.
139               qq! TITLE="Delete this payment from the database completely - not recommended"!.
140               qq!>delete</A>)!;
141   }
142
143   my $unapply = '';
144   if (    $cust_pay->closed !~ /^Y/i
145        && $conf->exists('unapplypayments')
146        && scalar(@cust_bill_pay)           ) {
147     $unapply = qq! (<A HREF="javascript:areyousure('!.
148                qq!${p}misc/unapply-cust_pay.cgi?!. $cust_pay->paynum.
149                qq!', 'Are you sure you want to unapply this payment?')"!.
150                qq! TITLE="Keep this payment, but dissociate it from the invoices it is currently applied against"!.
151                qq!>unapply</A>)!;
152   }
153
154   push @history, {
155     'date'    => $cust_pay->_date,
156     'desc'    => $pre. "Payment$post$info$desc".
157                  "$apply$refund$void$delete$unapply",
158     'payment' => $cust_pay->paid,
159     'target'  => $target,
160   };
161 }
162
163 #voided payments
164 foreach my $cust_pay_void ($cust_main->cust_pay_void) {
165
166   my $payby = $cust_pay_void->payby;
167   my $payinfo = $payby eq 'CARD'
168                   ? $cust_pay_void->payinfo_masked
169                   : $cust_pay_void->payinfo;
170
171   $payby =~ s/^BILL$/Check #/ if $payinfo;
172   $payby =~ s/^CHEK$/Electronic check /;
173   $payby =~ s/^BILL$//;
174   $payby =~ s/^(CARD|COMP)$/$1 /;
175   my $info = $payby ? " ($payby$payinfo)" : '';
176
177   push @history, {
178     'date'   => $cust_pay_void->_date,
179     'desc'   => "<DEL>Payment $info</DEL> <I>voided ".
180                 time2str("%D", $cust_pay_void->void_date).
181                 " by ". $cust_pay_void->otaker. '</i>',
182     'void_payment' => $cust_pay_void->paid,
183   };
184
185 }
186
187 #credits (some false laziness w/payments)
188 foreach my $cust_credit ($cust_main->cust_credit) {
189
190   my @cust_credit_bill = $cust_credit->cust_credit_bill;
191   my @cust_credit_refund = $cust_credit->cust_credit_refund;
192
193   my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
194   if (    scalar(@cust_credit_bill)   == 0
195        && scalar(@cust_credit_refund) == 0 ) {
196     #completely unapplied
197     $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
198     $post = '</FONT></B>';
199     $apply = qq! (<A HREF="${p}edit/cust_credit_bill.cgi?!.
200              $cust_credit->crednum. '">apply</A>)';
201   } elsif (    scalar(@cust_credit_bill)   == 1
202             && scalar(@cust_credit_refund) == 0
203             && $cust_credit->credited == 0      ) {
204     #applied to one invoice, the usual situation
205     $desc = ' applied to Invoice #'. $cust_credit_bill[0]->invnum;
206   } elsif (    scalar(@cust_credit_bill)   == 0
207             && scalar(@cust_credit_refund) == 1
208             && $cust_credit->credited == 0      ) {
209     #applied to one refund
210     $desc = ' refunded on '.  time2str("%D", $cust_credit_refund[0]->_date);
211   } else {
212     #complicated
213     $desc = '<BR>';
214     foreach my $app ( sort { $a->_date <=> $b->_date }
215                            ( @cust_credit_bill, @cust_credit_refund ) ) {
216       if ( $app->isa('FS::cust_credit_bill') ) {
217         $desc .= '&nbsp;&nbsp;'.
218                  '$'. $app->amount.
219                  ' applied to Invoice #'. $app->invnum.
220                  '<BR>';
221                  #' on '. time2str("%D", $app->_date).
222       } elsif ( $app->isa('FS::cust_credit_refund') ) {
223         $desc .= '&nbsp;&nbsp;'.
224                  '$'. $app->amount.
225                  ' refunded on'. time2str("%D", $app->_date).
226                  '<BR>';
227       } else {
228         die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund";
229       }
230     }
231     if ( $cust_credit->credited > 0 ) {
232       $desc .= '&nbsp;&nbsp;<B><FONT COLOR="#FF0000">$'.
233                $cust_credit->credited. ' unapplied</FONT></B>'.
234                qq! (<A HREF="${p}edit/cust_credit_bill.cgi?!.
235                $cust_credit->crednum. '">apply</A>)'.
236                '<BR>';
237     }
238   }
239 #
240   my $delete = '';
241   if ( $cust_credit->closed !~ /^Y/i && $conf->exists('deletecredits') ) {
242     $delete = qq! (<A HREF="javascript:areyousure('!.
243               qq!${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum.
244               qq!', 'Are you sure you want to delete this credit?')">!.
245               qq!delete</A>)!;
246   }
247   
248   my $unapply = '';
249   if (    $cust_credit->closed !~ /^Y/i
250        && $conf->exists('unapplycredits')
251        && scalar(@cust_credit_bill)       ) {
252     $unapply = qq! (<A HREF="javascript:areyousure('!.
253                qq!${p}misc/unapply-cust_credit.cgi?!. $cust_credit->crednum.
254                qq!', 'Are you sure you want to unapply this credit?')">!.
255                qq!unapply</A>)!;
256   }
257   
258   push @history, {
259     'date'   => $cust_credit->_date,
260     'desc'   => $pre. "Credit$post by ". $cust_credit->otaker.
261                 ' ('. $cust_credit->reason. ')'.
262                 "$desc$apply$delete$unapply",
263     'credit' => $cust_credit->amount,
264   };
265
266 }
267
268 #refunds
269 foreach my $cust_refund ($cust_main->cust_refund) {
270
271   my $payby = $cust_refund->payby;
272   my $payinfo = $payby eq 'CARD'
273                   ? $cust_refund->payinfo_masked
274                   : $cust_refund->payinfo;
275
276   $payby =~ s/^BILL$/Check #/ if $payinfo;
277   $payby =~ s/^CHEK$/Electronic check /;
278   $payby =~ s/^(CARD|COMP)$/$1 /;
279
280   push @history, {
281     'date'   => $cust_refund->_date,
282     'desc'   => "Refund ($payby$payinfo) by ". $cust_refund->otaker,
283     'refund' => $cust_refund->refund,
284   };
285
286 }
287
288 %>
289
290 <%= table() %>
291 <TR>
292   <TH>Date</TH>
293   <TH>Description</TH>
294   <TH><FONT SIZE=-1>Charge</FONT></TH>
295   <TH><FONT SIZE=-1>Payment</FONT></TH>
296   <TH><FONT SIZE=-1>In-house<BR>Credit</FONT></TH>
297   <TH><FONT SIZE=-1>Refund</FONT></TH>
298   <TH><FONT SIZE=-1>Balance</FONT></TH>
299 </TR>
300
301 <%
302 #display payment history
303
304 my %target;
305 my $balance = 0;
306 foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
307
308   my $charge  = exists($item->{'charge'})
309                   ? sprintf('$%.2f', $item->{'charge'})
310                   : '';
311   my $payment = exists($item->{'payment'})
312                   ? sprintf('-&nbsp;$%.2f', $item->{'payment'})
313                   : '';
314   $payment ||= sprintf('<DEL>-&nbsp;$%.2f</DEL>', $item->{'void_payment'})
315     if exists($item->{'void_payment'});
316   my $credit  = exists($item->{'credit'})
317                   ? sprintf('-&nbsp;$%.2f', $item->{'credit'})
318                   : '';
319   my $refund  = exists($item->{'refund'})
320                   ? sprintf('$%.2f', $item->{'refund'})
321                   : '';
322
323   my $target = exists($item->{'target'}) ? $item->{'target'} : '';
324
325   $balance += $item->{'charge'}  if exists $item->{'charge'};
326   $balance -= $item->{'payment'} if exists $item->{'payment'};
327   $balance -= $item->{'credit'}  if exists $item->{'credit'};
328   $balance += $item->{'refund'}  if exists $item->{'refund'};
329   $balance = sprintf("%.2f", $balance);
330   $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
331   ( my $showbalance = '$'. $balance ) =~ s/^\$\-/-&nbsp;\$/;
332
333 %>
334
335   <TR>
336     <TD>
337       <% unless ( !$target || $target{$target}++ ) { %>
338         <A NAME="<%= $target %>">
339       <% } %>
340       <%= time2str("%D",$item->{'date'}) %>
341       <% if ( $target && $target{$target} == 1 ) { %>
342         </A>
343       <% } %>
344       </FONT>
345     </TD>
346     <TD><%= $item->{'desc'} %></TD>
347     <TD ALIGN="right"><%= $charge  %></TD>
348     <TD ALIGN="right"><%= $payment %></TD>
349     <TD ALIGN="right"><%= $credit  %></TD>
350     <TD ALIGN="right"><%= $refund  %></TD>
351     <TD ALIGN="right"><%= $showbalance %></TD>
352   </TR>
353
354 <% } %>
355
356 </TABLE>
357