1 # ---------------------------------------------------------------
2 # Copyright (C) 2005 Georgia Public Library Service
3 # Bill Erickson <billserickson@gmail.com>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
16 package OpenILS::Application::Circ::Money;
17 use base qw/OpenILS::Application/;
18 use strict; use warnings;
19 use OpenILS::Application::AppUtils;
20 my $apputils = "OpenILS::Application::AppUtils";
21 my $U = "OpenILS::Application::AppUtils";
23 use OpenSRF::EX qw(:try);
27 use OpenSRF::Utils::Logger qw/:logger/;
28 use OpenILS::Utils::CStoreEditor qw/:funcs/;
29 use OpenILS::Utils::Penalty;
30 $Data::Dumper::Indent = 0;
32 __PACKAGE__->register_method(
33 method => "make_payments",
34 api_name => "open-ils.circ.money.payment",
36 desc => q/Create payments for a given user and set of transactions,
37 login must have CREATE_PAYMENT privileges.
38 If any payments fail, all are reverted back./,
40 {desc => 'Authtoken', type => 'string'},
41 {desc => q/Arguments Hash, supporting the following params:
48 where_process 1 to use processor, !1 for out-of-band
49 approval_code (for out-of-band payment)
50 type (for out-of-band payment)
51 number (for call to payment processor)
52 expire_month (for call to payment processor)
53 expire_year (for call to payment processor)
54 billing_first (for out-of-band payments and for call to payment processor)
55 billing_last (for out-of-band payments and for call to payment processor)
56 billing_address (for call to payment processor)
57 billing_city (for call to payment processor)
58 billing_state (for call to payment processor)
59 billing_zip (for call to payment processor)
60 note (if payments->{note} is blank, use this)
72 q{Array of payment IDs on success, event on failure. Event possibilities include:
74 Bad parameters were given to this API method itself.
76 REFUND_EXCEEDS_BALANCE
77 REFUND_EXCEEDS_DESK_PAYMENTS
78 CREDIT_PROCESSOR_NOT_SPECIFIED
79 Evergreen has not been set up to process CC payments.
80 CREDIT_PROCESSOR_NOT_ALLOWED
81 Evergreen has been incorrectly setup for CC payments.
82 CREDIT_PROCESSOR_NOT_ENABLED
83 Evergreen has been set up for CC payments, but an admin
84 has not explicitly enabled them.
85 CREDIT_PROCESSOR_BAD_PARAMS
86 Evergreen has been incorrectly setup for CC payments;
87 specifically, the login and/or password for the CC
88 processor weren't provided.
89 CREDIT_PROCESSOR_INVALID_CC_NUMBER
90 You have supplied a credit card number that Evergreen
91 has judged to be invalid even before attempting to contact
92 the payment processor.
93 CREDIT_PROCESSOR_DECLINED_TRANSACTION
94 We contacted the CC processor to attempt the charge, but
96 The error_message field of the event payload will
97 contain the payment processor's response. This
98 typically includes a message in plain English intended
99 for human consumption. In PayPal's case, the message
100 is preceded by an integer, a colon, and a space, so
101 a caller might take the 2nd match from /^(\d+: )?(.+)$/
102 to present to the user.
103 The payload also contains other fields from the payment
104 processor, but these are generally not user-friendly
106 CREDIT_PROCESSOR_SUCCESS_WO_RECORD
107 A payment was processed successfully, but couldn't be
108 recorded in Evergreen. This is _bad bad bad_, as it means
109 somebody made a payment but isn't getting credit for it.
110 See errors in the system log if this happens. Info from
111 the credit card transaction will also be available in the
112 event payload, although this probably won't be suitable for
113 staff client/OPAC display.
120 my($self, $client, $auth, $payments) = @_;
122 my $e = new_editor(authtoken => $auth, xact => 1);
123 return $e->die_event unless $e->checkauth;
125 my $type = $payments->{payment_type};
126 my $user_id = $payments->{userid};
127 my $credit = $payments->{patron_credit} || 0;
128 my $drawer = $e->requestor->wsid;
129 my $note = $payments->{note};
130 my $cc_args = $payments->{cc_args};
131 my $check_number = $payments->{check_number};
133 my $this_ou = $e->requestor->ws_ou;
136 # unless/until determined by payment processor API
137 my ($approval_code, $cc_processor, $cc_type) = (undef,undef,undef);
139 my $patron = $e->retrieve_actor_user($user_id) or return $e->die_event;
141 # A user is allowed to make credit card payments on his/her own behalf
142 # All other scenarious require permission
143 unless($type eq 'credit_card_payment' and $user_id == $e->requestor->id) {
144 return $e->die_event unless $e->allowed('CREATE_PAYMENT', $patron->home_ou);
147 # first collect the transactions and make sure the transaction
148 # user matches the requested user
150 for my $pay (@{$payments->{payments}}) {
151 my $xact_id = $pay->[0];
152 my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
153 or return $e->die_event;
155 if($xact->usr != $user_id) {
157 return OpenILS::Event->new('BAD_PARAMS', note => q/user does not match transaction/);
160 $xacts{$xact_id} = $xact;
165 for my $pay (@{$payments->{payments}}) {
166 my $transid = $pay->[0];
167 my $amount = $pay->[1];
168 $amount =~ s/\$//og; # just to be safe
169 my $trans = $xacts{$transid};
171 $total_paid += $amount;
173 $orgs{$U->xact_org($transid, $e)} = 1;
175 # A negative payment is a refund.
178 # Negative credit card payments are not allowed
179 if($type eq 'credit_card_payment') {
181 return OpenILS::Event->new(
183 note => q/Negative credit card payments not allowed/
187 # If the refund causes the transaction balance to exceed 0 dollars,
188 # we are in effect loaning the patron money. This is not allowed.
189 if( ($trans->balance_owed - $amount) > 0 ) {
191 return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
194 # Otherwise, make sure the refund does not exceed desk payments
195 # This is also not allowed
197 my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
198 $desk_total += $_->amount for @$desk_payments;
200 if( (-$amount) > $desk_total ) {
202 return OpenILS::Event->new(
203 'REFUND_EXCEEDS_DESK_PAYMENTS',
204 payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
208 my $payobj = "Fieldmapper::money::$type";
209 $payobj = $payobj->new;
211 $payobj->amount($amount);
212 $payobj->amount_collected($amount);
213 $payobj->xact($transid);
214 $payobj->note($note);
215 if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
216 $payobj->note($cc_args->{note});
219 if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
220 if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
221 if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
222 if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
224 # Store the last 4 digits of the CC number
225 if ($payobj->has_field('cc_number')) {
226 $payobj->cc_number(substr($cc_args->{number}, -4));
228 if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
229 if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
231 # Note: It is important not to set approval_code
232 # on the fieldmapper object yet.
234 push(@payment_objs, $payobj);
236 } # all payment objects have been created and inserted.
238 #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED ###
241 # After we try to externally process a credit card (if desired), we'll
242 # open a new transaction. We cannot leave one open while credit card
243 # processing might be happening, as it can easily time out the database
248 if($type eq 'credit_card_payment') {
249 $approval_code = $cc_args->{approval_code};
250 # If an approval code was not given, we'll need
251 # to call to the payment processor ourselves.
252 if ($cc_args->{where_process} == 1) {
253 return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
254 if not $cc_args->{number};
256 OpenILS::Application::Circ::CreditCard::process_payment({
257 "desc" => $cc_args->{note},
258 "amount" => $total_paid,
259 "patron_id" => $user_id,
260 "cc" => $cc_args->{number},
261 "expiration" => sprintf(
263 $cc_args->{expire_month},
264 $cc_args->{expire_year}
267 "first_name" => $cc_args->{billing_first},
268 "last_name" => $cc_args->{billing_last},
269 "address" => $cc_args->{billing_address},
270 "city" => $cc_args->{billing_city},
271 "state" => $cc_args->{billing_state},
272 "zip" => $cc_args->{billing_zip},
275 if ($U->event_code($response)) { # non-success
277 "Credit card payment for user $user_id failed: " .
278 $response->{"textcode"} . " " .
279 $response->{"payload"}->{"error_message"}
284 # We need to save this for later in case there's a failure on
285 # the EG side to store the processor's result.
286 $cc_payload = $response->{"payload"};
288 $approval_code = $cc_payload->{"authorization"};
289 $cc_type = $cc_payload->{"card_type"};
290 $cc_processor = $cc_payload->{"processor"};
291 $logger->info("Credit card payment for user $user_id succeeded");
294 return OpenILS::Event->new(
295 'BAD_PARAMS', note => 'Need approval code'
296 ) if not $cc_args->{approval_code};
300 ### RE-OPEN TRANSACTION HERE ###
304 # create payment records
305 my $create_money_method = "create_money_" . $type;
306 for my $payment (@payment_objs) {
307 # update the transaction if it's done
308 my $amount = $payment->amount;
309 my $transid = $payment->xact;
310 my $trans = $xacts{$transid};
311 if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
312 # Any overpay on this transaction goes directly into patron
313 # credit making payment with existing patron credit.
314 $credit -= $amount if $type eq 'credit_payment';
318 my $circ = $e->retrieve_action_circulation($transid);
320 if(!$circ || $circ->stop_fines) {
321 # If this is a circulation, we can't close the transaction
322 # unless stop_fines is set.
323 $trans = $e->retrieve_money_billable_transaction($transid);
324 $trans->xact_finish("now");
325 if (!$e->update_money_billable_transaction($trans)) {
326 return _recording_failure(
327 $e, "update_money_billable_transaction() failed",
328 $payment, $cc_payload
334 $payment->approval_code($approval_code) if $approval_code;
335 $payment->cc_type($cc_type) if $cc_type;
336 $payment->cc_processor($cc_processor) if $cc_processor;
337 $payment->cc_first_name($cc_args->{'billing_first'}) if $cc_args->{'billing_first'};
338 $payment->cc_last_name($cc_args->{'billing_last'}) if $cc_args->{'billing_last'};
339 if (!$e->$create_money_method($payment)) {
340 return _recording_failure(
341 $e, "$create_money_method failed", $payment, $cc_payload
345 push(@payment_ids, $payment->id);
348 my $evt = _update_patron_credit($e, $patron, $credit);
350 return _recording_failure(
351 $e, "_update_patron_credit() failed", undef, $cc_payload
355 for my $org_id (keys %orgs) {
356 # calculate penalties for each of the affected orgs
357 $evt = OpenILS::Utils::Penalty->calculate_penalties(
358 $e, $user_id, $org_id
361 return _recording_failure(
362 $e, "calculate_penalties() failed", undef, $cc_payload
368 return \@payment_ids;
371 sub _recording_failure {
372 my ($e, $msg, $payment, $payload) = @_;
374 if ($payload) { # If the payment processor already accepted a payment:
375 $logger->error($msg);
376 $logger->error("Payment processor payload: " . Dumper($payload));
377 # payment shouldn't contain CC number
378 $logger->error("Payment: " . Dumper($payment)) if $payment;
382 return new OpenILS::Event(
383 "CREDIT_PROCESSOR_SUCCESS_WO_RECORD",
384 "payload" => $payload
386 } else { # Otherwise, the problem is somewhat less severe:
388 $logger->warn("Payment: " . Dumper($payment)) if $payment;
389 return $e->die_event;
393 sub _update_patron_credit {
394 my($e, $patron, $credit) = @_;
395 return undef if $credit == 0;
396 $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
397 return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
398 $e->update_actor_user($patron) or return $e->die_event;
403 __PACKAGE__->register_method(
404 method => "retrieve_payments",
405 api_name => "open-ils.circ.money.payment.retrieve.all_",
406 notes => "Returns a list of payments attached to a given transaction"
408 sub retrieve_payments {
409 my( $self, $client, $login, $transid ) = @_;
412 $apputils->checksesperm($login, 'VIEW_TRANSACTION');
415 # XXX the logic here is wrong.. we need to check the owner of the transaction
416 # to make sure the requestor has access
418 # XXX grab the view, for each object in the view, grab the real object
420 return $apputils->simplereq(
422 'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
426 __PACKAGE__->register_method(
427 method => "retrieve_payments2",
429 api_name => "open-ils.circ.money.payment.retrieve.all",
430 notes => "Returns a list of payments attached to a given transaction"
433 sub retrieve_payments2 {
434 my( $self, $client, $login, $transid ) = @_;
436 my $e = new_editor(authtoken=>$login);
437 return $e->event unless $e->checkauth;
438 return $e->event unless $e->allowed('VIEW_TRANSACTION');
441 my $pmnts = $e->search_money_payment({ xact => $transid });
443 my $type = $_->payment_type;
444 my $meth = "retrieve_money_$type";
445 my $p = $e->$meth($_->id) or return $e->event;
446 $p->payment_type($type);
447 $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
448 if $p->has_field('cash_drawer');
449 push( @payments, $p );
455 __PACKAGE__->register_method(
456 method => "format_payment_receipt",
457 api_name => "open-ils.circ.money.payment_receipt.print",
459 desc => 'Returns a printable receipt for the specified payments',
461 { desc => 'Authentication token', type => 'string'},
462 { desc => 'Payment ID or array of payment IDs', type => 'number' },
465 desc => q/An action_trigger.event object or error event./,
470 __PACKAGE__->register_method(
471 method => "format_payment_receipt",
472 api_name => "open-ils.circ.money.payment_receipt.email",
474 desc => 'Emails a receipt for the specified payments to the user associated with the first payment',
476 { desc => 'Authentication token', type => 'string'},
477 { desc => 'Payment ID or array of payment IDs', type => 'number' },
480 desc => q/Undefined on success, otherwise an error event./,
486 sub format_payment_receipt {
487 my($self, $conn, $auth, $mp_id) = @_;
490 if (ref $mp_id ne 'ARRAY') {
491 $mp_ids = [ $mp_id ];
496 my $for_print = ($self->api_name =~ /print/);
497 my $for_email = ($self->api_name =~ /email/);
498 my $e = new_editor(authtoken => $auth);
499 return $e->event unless $e->checkauth;
502 for my $id (@$mp_ids) {
504 my $payment = $e->retrieve_money_payment([
512 ]) or return OpenILS::Event->new('MP_NOT_FOUND');
514 return $e->event unless $e->allowed('VIEW_TRANSACTION', $payment->xact->usr->home_ou);
516 push @$payments, $payment;
521 return $U->fire_object_event(undef, 'money.format.payment_receipt.print', $payments, $$payments[0]->xact->usr->home_ou);
523 } elsif ($for_email) {
525 for my $p (@$payments) {
526 $U->create_events_for_hook('money.format.payment_receipt.email', $p, $p->xact->usr->home_ou, undef, undef, 1);
533 __PACKAGE__->register_method(
534 method => "create_grocery_bill",
535 api_name => "open-ils.circ.money.grocery.create",
537 Creates a new grocery transaction using the transaction object provided
538 PARAMS: (login_session, money.grocery (mg) object)
541 sub create_grocery_bill {
542 my( $self, $client, $login, $transaction ) = @_;
544 my( $staff, $evt ) = $apputils->checkses($login);
546 $evt = $apputils->check_perms($staff->id,
547 $transaction->billing_location, 'CREATE_TRANSACTION' );
551 $logger->activity("Creating grocery bill " . Dumper($transaction) );
553 $transaction->clear_id;
554 my $session = $apputils->start_db_session;
555 my $transid = $session->request(
556 'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
558 throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
560 $logger->debug("Created new grocery transaction $transid");
562 $apputils->commit_db_session($session);
564 my $e = new_editor(xact=>1);
565 $evt = _check_open_xact($e, $transid);
573 __PACKAGE__->register_method(
574 method => 'fetch_reservation',
575 api_name => 'open-ils.circ.booking.reservation.retrieve'
577 sub fetch_reservation {
578 my( $self, $conn, $auth, $id ) = @_;
579 my $e = new_editor(authtoken=>$auth);
580 return $e->event unless $e->checkauth;
581 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
582 my $g = $e->retrieve_booking_reservation($id)
587 __PACKAGE__->register_method(
588 method => 'fetch_grocery',
589 api_name => 'open-ils.circ.money.grocery.retrieve'
592 my( $self, $conn, $auth, $id ) = @_;
593 my $e = new_editor(authtoken=>$auth);
594 return $e->event unless $e->checkauth;
595 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
596 my $g = $e->retrieve_money_grocery($id)
602 __PACKAGE__->register_method(
603 method => "billing_items",
604 api_name => "open-ils.circ.money.billing.retrieve.all",
607 desc => 'Returns a list of billing items for the given transaction ID. ' .
608 'If the operator is not the owner of the transaction, the VIEW_TRANSACTION permission is required.',
610 { desc => 'Authentication token', type => 'string'},
611 { desc => 'Transaction ID', type => 'number'}
614 desc => 'Transaction object, event on error'
620 my( $self, $client, $login, $transid ) = @_;
622 my( $trans, $evt ) = $U->fetch_billable_xact($transid);
626 ($staff, $evt ) = $apputils->checkses($login);
629 if($staff->id ne $trans->usr) {
630 $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
634 return $apputils->simplereq( 'open-ils.cstore',
635 'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
639 __PACKAGE__->register_method(
640 method => "billing_items_create",
641 api_name => "open-ils.circ.money.billing.create",
643 Creates a new billing line item
644 PARAMS( login, bill_object (mb) )
647 sub billing_items_create {
648 my( $self, $client, $login, $billing ) = @_;
650 my $e = new_editor(authtoken => $login, xact => 1);
651 return $e->die_event unless $e->checkauth;
652 return $e->die_event unless $e->allowed('CREATE_BILL');
654 my $xact = $e->retrieve_money_billable_transaction($billing->xact)
655 or return $e->die_event;
657 # if the transaction was closed, re-open it
658 if($xact->xact_finish) {
659 $xact->clear_xact_finish;
660 $e->update_money_billable_transaction($xact)
661 or return $e->die_event;
664 my $amt = $billing->amount;
666 $billing->amount($amt);
668 $e->create_money_billing($billing) or return $e->die_event;
669 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id));
677 __PACKAGE__->register_method(
678 method => 'void_bill',
679 api_name => 'open-ils.circ.money.billing.void',
682 @param authtoken Login session key
683 @param billid Id for the bill to void. This parameter may be repeated to reference other bills.
684 @return 1 on success, Event on error
688 my( $s, $c, $authtoken, @billids ) = @_;
690 my $e = new_editor( authtoken => $authtoken, xact => 1 );
691 return $e->die_event unless $e->checkauth;
692 return $e->die_event unless $e->allowed('VOID_BILLING');
695 for my $billid (@billids) {
697 my $bill = $e->retrieve_money_billing($billid)
698 or return $e->die_event;
700 my $xact = $e->retrieve_money_billable_transaction($bill->xact)
701 or return $e->die_event;
703 if($U->is_true($bill->voided)) {
705 return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
708 my $org = $U->xact_org($bill->xact, $e);
709 $users{$xact->usr} = {} unless $users{$xact->usr};
710 $users{$xact->usr}->{$org} = 1;
713 $bill->voider($e->requestor->id);
714 $bill->void_time('now');
716 $e->update_money_billing($bill) or return $e->die_event;
717 my $evt = _check_open_xact($e, $bill->xact, $xact);
721 # calculate penalties for all user/org combinations
722 for my $user_id (keys %users) {
723 for my $org_id (keys %{$users{$user_id}}) {
724 OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
732 __PACKAGE__->register_method(
733 method => 'edit_bill_note',
734 api_name => 'open-ils.circ.money.billing.note.edit',
736 Edits the note for a bill
737 @param authtoken Login session key
738 @param note The replacement note for the bills we're editing
739 @param billid Id for the bill to edit the note of. This parameter may be repeated to reference other bills.
740 @return 1 on success, Event on error
744 my( $s, $c, $authtoken, $note, @billids ) = @_;
746 my $e = new_editor( authtoken => $authtoken, xact => 1 );
747 return $e->die_event unless $e->checkauth;
748 return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
750 for my $billid (@billids) {
752 my $bill = $e->retrieve_money_billing($billid)
753 or return $e->die_event;
756 # FIXME: Does this get audited? Need some way so that the original creator of the bill does not get credit/blame for the new note.
758 $e->update_money_billing($bill) or return $e->die_event;
765 __PACKAGE__->register_method(
766 method => 'edit_payment_note',
767 api_name => 'open-ils.circ.money.payment.note.edit',
769 Edits the note for a payment
770 @param authtoken Login session key
771 @param note The replacement note for the payments we're editing
772 @param paymentid Id for the payment to edit the note of. This parameter may be repeated to reference other payments.
773 @return 1 on success, Event on error
776 sub edit_payment_note {
777 my( $s, $c, $authtoken, $note, @paymentids ) = @_;
779 my $e = new_editor( authtoken => $authtoken, xact => 1 );
780 return $e->die_event unless $e->checkauth;
781 return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
783 for my $paymentid (@paymentids) {
785 my $payment = $e->retrieve_money_payment($paymentid)
786 or return $e->die_event;
788 $payment->note($note);
789 # FIXME: Does this get audited? Need some way so that the original taker of the payment does not get credit/blame for the new note.
791 $e->update_money_payment($payment) or return $e->die_event;
798 sub _check_open_xact {
799 my( $editor, $xactid, $xact ) = @_;
801 # Grab the transaction
802 $xact ||= $editor->retrieve_money_billable_transaction($xactid);
803 return $editor->event unless $xact;
804 $xactid ||= $xact->id;
806 # grab the summary and see how much is owed on this transaction
807 my ($summary) = $U->fetch_mbts($xactid, $editor);
809 # grab the circulation if it is a circ;
810 my $circ = $editor->retrieve_action_circulation($xactid);
812 # If nothing is owed on the transaction but it is still open
813 # and this transaction is not an open circulation, close it
815 ( $summary->balance_owed == 0 and ! $xact->xact_finish ) and
816 ( !$circ or $circ->stop_fines )) {
818 $logger->info("closing transaction ".$xact->id. ' becauase balance_owed == 0');
819 $xact->xact_finish('now');
820 $editor->update_money_billable_transaction($xact)
821 or return $editor->event;
825 # If money is owed or a refund is due on the xact and xact_finish
826 # is set, clear it (to reopen the xact) and update
827 if( $summary->balance_owed != 0 and $xact->xact_finish ) {
828 $logger->info("re-opening transaction ".$xact->id. ' becauase balance_owed != 0');
829 $xact->clear_xact_finish;
830 $editor->update_money_billable_transaction($xact)
831 or return $editor->event;
838 __PACKAGE__->register_method (
839 method => 'fetch_mbts',
841 api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
844 my( $self, $conn, $auth, $id) = @_;
846 my $e = new_editor(xact => 1, authtoken=>$auth);
847 return $e->event unless $e->checkauth;
848 my ($mbts) = $U->fetch_mbts($id, $e);
850 my $user = $e->retrieve_actor_user($mbts->usr)
851 or return $e->die_event;
853 return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
859 __PACKAGE__->register_method(
860 method => 'desk_payments',
861 api_name => 'open-ils.circ.money.org_unit.desk_payments'
864 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
865 my $e = new_editor(authtoken=>$auth);
866 return $e->event unless $e->checkauth;
867 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
868 my $data = $U->storagereq(
869 'open-ils.storage.money.org_unit.desk_payments.atomic',
870 $org, $start_date, $end_date );
872 $_->workstation( $_->workstation->name ) for(@$data);
877 __PACKAGE__->register_method(
878 method => 'user_payments',
879 api_name => 'open-ils.circ.money.org_unit.user_payments'
883 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
884 my $e = new_editor(authtoken=>$auth);
885 return $e->event unless $e->checkauth;
886 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
887 my $data = $U->storagereq(
888 'open-ils.storage.money.org_unit.user_payments.atomic',
889 $org, $start_date, $end_date );
892 $e->retrieve_actor_card($_->usr->card)->barcode);
894 $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
900 __PACKAGE__->register_method(
901 method => 'retrieve_credit_payable_balance',
902 api_name => 'open-ils.circ.credit.payable_balance.retrieve',
905 desc => q/Returns the total amount the patron can pay via credit card/,
907 { desc => 'Authentication token', type => 'string' },
908 { desc => 'User id', type => 'number' }
910 return => { desc => 'The ID of the new provider' }
914 sub retrieve_credit_payable_balance {
915 my ( $self, $conn, $auth, $user_id ) = @_;
916 my $e = new_editor(authtoken => $auth);
917 return $e->event unless $e->checkauth;
919 my $user = $e->retrieve_actor_user($user_id)
922 if($e->requestor->id != $user_id) {
923 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou)
926 my $circ_orgs = $e->json_query({
927 "select" => {circ => ["circ_lib"]},
929 "where" => {usr => $user_id, xact_finish => undef},
933 my $groc_orgs = $e->json_query({
934 "select" => {mg => ["billing_location"]},
936 "where" => {usr => $user_id, xact_finish => undef},
941 for my $org ( @$circ_orgs, @$groc_orgs ) {
942 my $o = $org->{billing_location};
943 $o = $org->{circ_lib} unless $o;
944 next if $hash{$o}; # was $hash{$org}, but that doesn't make sense. $org is a hashref and $o gets added in the next line.
945 $hash{$o} = $U->ou_ancestor_setting_value($o, 'credit.payments.allow', $e);
948 my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
949 $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
952 OpenILS::Application::AppUtils->simplereq('open-ils.actor',
953 'open-ils.actor.user.transactions.have_charge', $auth, $user_id);
957 for my $xact (@$xact_summaries) {
959 # make two lists and grab them in batch XXX
960 if ( $xact->xact_type eq 'circulation' ) {
961 my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
962 next unless grep { $_ == $circ->circ_lib } @credit_orgs;
964 } elsif ($xact->xact_type eq 'grocery') {
965 my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
966 next unless grep { $_ == $bill->billing_location } @credit_orgs;
967 } elsif ($xact->xact_type eq 'reservation') {
968 my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
969 next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
971 $sum += $xact->balance_owed();