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 call to payment processor)
55 billing_last (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{1 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 ###
303 # create payment records
304 my $create_money_method = "create_money_" . $type;
305 for my $payment (@payment_objs) {
306 # update the transaction if it's done
307 my $amount = $payment->amount;
308 my $transid = $payment->xact;
309 my $trans = $xacts{$transid};
310 if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
311 # Any overpay on this transaction goes directly into patron
312 # credit making payment with existing patron credit.
313 $credit -= $amount if $type eq 'credit_payment';
317 my $circ = $e->retrieve_action_circulation($transid);
319 if(!$circ || $circ->stop_fines) {
320 # If this is a circulation, we can't close the transaction
321 # unless stop_fines is set.
322 $trans = $e->retrieve_money_billable_transaction($transid);
323 $trans->xact_finish("now");
324 if (!$e->update_money_billable_transaction($trans)) {
325 return _recording_failure(
326 $e, "update_money_billable_transaction() failed",
327 $payment, $cc_payload
333 $payment->approval_code($approval_code) if $approval_code;
334 $payment->cc_type($cc_type) if $cc_type;
335 $payment->cc_processor($cc_processor) if $cc_processor;
336 if (!$e->$create_money_method($payment)) {
337 return _recording_failure(
338 $e, "$create_money_method failed", $payment, $cc_payload
343 my $evt = _update_patron_credit($e, $patron, $credit);
345 return _recording_failure(
346 $e, "_update_patron_credit() failed", undef, $cc_payload
350 for my $org_id (keys %orgs) {
351 # calculate penalties for each of the affected orgs
352 $evt = OpenILS::Utils::Penalty->calculate_penalties(
353 $e, $user_id, $org_id
356 return _recording_failure(
357 $e, "calculate_penalties() failed", undef, $cc_payload
366 sub _recording_failure {
367 my ($e, $msg, $payment, $payload) = @_;
369 if ($payload) { # If the payment processor already accepted a payment:
370 $logger->error($msg);
371 $logger->error("Payment processor payload: " . Dumper($payload));
372 # payment shouldn't contain CC number
373 $logger->error("Payment: " . Dumper($payment)) if $payment;
377 return new OpenILS::Event(
378 "CREDIT_PROCESSOR_SUCCESS_WO_RECORD",
379 "payload" => $payload
381 } else { # Otherwise, the problem is somewhat less severe:
383 $logger->warn("Payment: " . Dumper($payment));
384 return $e->die_event;
388 sub _update_patron_credit {
389 my($e, $patron, $credit) = @_;
390 return undef if $credit == 0;
391 $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
392 return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
393 $e->update_actor_user($patron) or return $e->die_event;
398 __PACKAGE__->register_method(
399 method => "retrieve_payments",
400 api_name => "open-ils.circ.money.payment.retrieve.all_",
401 notes => "Returns a list of payments attached to a given transaction"
403 sub retrieve_payments {
404 my( $self, $client, $login, $transid ) = @_;
407 $apputils->checksesperm($login, 'VIEW_TRANSACTION');
410 # XXX the logic here is wrong.. we need to check the owner of the transaction
411 # to make sure the requestor has access
413 # XXX grab the view, for each object in the view, grab the real object
415 return $apputils->simplereq(
417 'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
421 __PACKAGE__->register_method(
422 method => "retrieve_payments2",
424 api_name => "open-ils.circ.money.payment.retrieve.all",
425 notes => "Returns a list of payments attached to a given transaction"
428 sub retrieve_payments2 {
429 my( $self, $client, $login, $transid ) = @_;
431 my $e = new_editor(authtoken=>$login);
432 return $e->event unless $e->checkauth;
433 return $e->event unless $e->allowed('VIEW_TRANSACTION');
436 my $pmnts = $e->search_money_payment({ xact => $transid });
438 my $type = $_->payment_type;
439 my $meth = "retrieve_money_$type";
440 my $p = $e->$meth($_->id) or return $e->event;
441 $p->payment_type($type);
442 $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
443 if $p->has_field('cash_drawer');
444 push( @payments, $p );
450 __PACKAGE__->register_method(
451 method => "format_payment_receipt",
452 api_name => "open-ils.circ.money.payment_receipt.print",
454 desc => 'Returns a printable receipt for the specified payments',
456 { desc => 'Authentication token', type => 'string'},
457 { desc => 'Payment ID or array of payment IDs', type => 'number' },
460 desc => q/An action_trigger.event object or error event./,
465 __PACKAGE__->register_method(
466 method => "format_payment_receipt",
467 api_name => "open-ils.circ.money.payment_receipt.email",
469 desc => 'Emails a receipt for the specified payments to the user associated with the first payment',
471 { desc => 'Authentication token', type => 'string'},
472 { desc => 'Payment ID or array of payment IDs', type => 'number' },
475 desc => q/Undefined on success, otherwise an error event./,
481 sub format_payment_receipt {
482 my($self, $conn, $auth, $mp_id) = @_;
485 if (ref $mp_id ne 'ARRAY') {
486 $mp_ids = [ $mp_id ];
491 my $for_print = ($self->api_name =~ /print/);
492 my $for_email = ($self->api_name =~ /email/);
493 my $e = new_editor(authtoken => $auth);
494 return $e->event unless $e->checkauth;
497 for my $id (@$mp_ids) {
499 my $payment = $e->retrieve_money_payment([
507 ]) or return OpenILS::Event->new('MP_NOT_FOUND');
509 return $e->event unless $e->allowed('VIEW_TRANSACTION', $payment->xact->usr->home_ou);
511 push @$payments, $payment;
516 return $U->fire_object_event(undef, 'money.format.payment_receipt.print', $payments, $$payments[0]->xact->usr->home_ou);
518 } elsif ($for_email) {
520 for my $p (@$payments) {
521 $U->create_events_for_hook('money.format.payment_receipt.email', $p, $p->xact->usr->home_ou, undef, undef, 1);
528 __PACKAGE__->register_method(
529 method => "create_grocery_bill",
530 api_name => "open-ils.circ.money.grocery.create",
532 Creates a new grocery transaction using the transaction object provided
533 PARAMS: (login_session, money.grocery (mg) object)
536 sub create_grocery_bill {
537 my( $self, $client, $login, $transaction ) = @_;
539 my( $staff, $evt ) = $apputils->checkses($login);
541 $evt = $apputils->check_perms($staff->id,
542 $transaction->billing_location, 'CREATE_TRANSACTION' );
546 $logger->activity("Creating grocery bill " . Dumper($transaction) );
548 $transaction->clear_id;
549 my $session = $apputils->start_db_session;
550 my $transid = $session->request(
551 'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
553 throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
555 $logger->debug("Created new grocery transaction $transid");
557 $apputils->commit_db_session($session);
559 my $e = new_editor(xact=>1);
560 $evt = _check_open_xact($e, $transid);
568 __PACKAGE__->register_method(
569 method => 'fetch_reservation',
570 api_name => 'open-ils.circ.booking.reservation.retrieve'
572 sub fetch_reservation {
573 my( $self, $conn, $auth, $id ) = @_;
574 my $e = new_editor(authtoken=>$auth);
575 return $e->event unless $e->checkauth;
576 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
577 my $g = $e->retrieve_booking_reservation($id)
582 __PACKAGE__->register_method(
583 method => 'fetch_grocery',
584 api_name => 'open-ils.circ.money.grocery.retrieve'
587 my( $self, $conn, $auth, $id ) = @_;
588 my $e = new_editor(authtoken=>$auth);
589 return $e->event unless $e->checkauth;
590 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
591 my $g = $e->retrieve_money_grocery($id)
597 __PACKAGE__->register_method(
598 method => "billing_items",
599 api_name => "open-ils.circ.money.billing.retrieve.all",
602 desc => 'Returns a list of billing items for the given transaction ID. ' .
603 'If the operator is not the owner of the transaction, the VIEW_TRANSACTION permission is required.',
605 { desc => 'Authentication token', type => 'string'},
606 { desc => 'Transaction ID', type => 'number'}
609 desc => 'Transaction object, event on error'
615 my( $self, $client, $login, $transid ) = @_;
617 my( $trans, $evt ) = $U->fetch_billable_xact($transid);
621 ($staff, $evt ) = $apputils->checkses($login);
624 if($staff->id ne $trans->usr) {
625 $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
629 return $apputils->simplereq( 'open-ils.cstore',
630 'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
634 __PACKAGE__->register_method(
635 method => "billing_items_create",
636 api_name => "open-ils.circ.money.billing.create",
638 Creates a new billing line item
639 PARAMS( login, bill_object (mb) )
642 sub billing_items_create {
643 my( $self, $client, $login, $billing ) = @_;
645 my $e = new_editor(authtoken => $login, xact => 1);
646 return $e->die_event unless $e->checkauth;
647 return $e->die_event unless $e->allowed('CREATE_BILL');
649 my $xact = $e->retrieve_money_billable_transaction($billing->xact)
650 or return $e->die_event;
652 # if the transaction was closed, re-open it
653 if($xact->xact_finish) {
654 $xact->clear_xact_finish;
655 $e->update_money_billable_transaction($xact)
656 or return $e->die_event;
659 my $amt = $billing->amount;
661 $billing->amount($amt);
663 $e->create_money_billing($billing) or return $e->die_event;
664 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id));
672 __PACKAGE__->register_method(
673 method => 'void_bill',
674 api_name => 'open-ils.circ.money.billing.void',
677 @param authtoken Login session key
678 @param billid Id for the bill to void. This parameter may be repeated to reference other bills.
679 @return 1 on success, Event on error
683 my( $s, $c, $authtoken, @billids ) = @_;
685 my $e = new_editor( authtoken => $authtoken, xact => 1 );
686 return $e->die_event unless $e->checkauth;
687 return $e->die_event unless $e->allowed('VOID_BILLING');
690 for my $billid (@billids) {
692 my $bill = $e->retrieve_money_billing($billid)
693 or return $e->die_event;
695 my $xact = $e->retrieve_money_billable_transaction($bill->xact)
696 or return $e->die_event;
698 if($U->is_true($bill->voided)) {
700 return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
703 my $org = $U->xact_org($bill->xact, $e);
704 $users{$xact->usr} = {} unless $users{$xact->usr};
705 $users{$xact->usr}->{$org} = 1;
708 $bill->voider($e->requestor->id);
709 $bill->void_time('now');
711 $e->update_money_billing($bill) or return $e->die_event;
712 my $evt = _check_open_xact($e, $bill->xact, $xact);
716 # calculate penalties for all user/org combinations
717 for my $user_id (keys %users) {
718 for my $org_id (keys %{$users{$user_id}}) {
719 OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
727 __PACKAGE__->register_method(
728 method => 'edit_bill_note',
729 api_name => 'open-ils.circ.money.billing.note.edit',
731 Edits the note for a bill
732 @param authtoken Login session key
733 @param note The replacement note for the bills we're editing
734 @param billid Id for the bill to edit the note of. This parameter may be repeated to reference other bills.
735 @return 1 on success, Event on error
739 my( $s, $c, $authtoken, $note, @billids ) = @_;
741 my $e = new_editor( authtoken => $authtoken, xact => 1 );
742 return $e->die_event unless $e->checkauth;
743 return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
745 for my $billid (@billids) {
747 my $bill = $e->retrieve_money_billing($billid)
748 or return $e->die_event;
751 # 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.
753 $e->update_money_billing($bill) or return $e->die_event;
760 __PACKAGE__->register_method(
761 method => 'edit_payment_note',
762 api_name => 'open-ils.circ.money.payment.note.edit',
764 Edits the note for a payment
765 @param authtoken Login session key
766 @param note The replacement note for the payments we're editing
767 @param paymentid Id for the payment to edit the note of. This parameter may be repeated to reference other payments.
768 @return 1 on success, Event on error
771 sub edit_payment_note {
772 my( $s, $c, $authtoken, $note, @paymentids ) = @_;
774 my $e = new_editor( authtoken => $authtoken, xact => 1 );
775 return $e->die_event unless $e->checkauth;
776 return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
778 for my $paymentid (@paymentids) {
780 my $payment = $e->retrieve_money_payment($paymentid)
781 or return $e->die_event;
783 $payment->note($note);
784 # 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.
786 $e->update_money_payment($payment) or return $e->die_event;
793 sub _check_open_xact {
794 my( $editor, $xactid, $xact ) = @_;
796 # Grab the transaction
797 $xact ||= $editor->retrieve_money_billable_transaction($xactid);
798 return $editor->event unless $xact;
799 $xactid ||= $xact->id;
801 # grab the summary and see how much is owed on this transaction
802 my ($summary) = $U->fetch_mbts($xactid, $editor);
804 # grab the circulation if it is a circ;
805 my $circ = $editor->retrieve_action_circulation($xactid);
807 # If nothing is owed on the transaction but it is still open
808 # and this transaction is not an open circulation, close it
810 ( $summary->balance_owed == 0 and ! $xact->xact_finish ) and
811 ( !$circ or $circ->stop_fines )) {
813 $logger->info("closing transaction ".$xact->id. ' becauase balance_owed == 0');
814 $xact->xact_finish('now');
815 $editor->update_money_billable_transaction($xact)
816 or return $editor->event;
820 # If money is owed or a refund is due on the xact and xact_finish
821 # is set, clear it (to reopen the xact) and update
822 if( $summary->balance_owed != 0 and $xact->xact_finish ) {
823 $logger->info("re-opening transaction ".$xact->id. ' becauase balance_owed != 0');
824 $xact->clear_xact_finish;
825 $editor->update_money_billable_transaction($xact)
826 or return $editor->event;
833 __PACKAGE__->register_method (
834 method => 'fetch_mbts',
836 api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
839 my( $self, $conn, $auth, $id) = @_;
841 my $e = new_editor(xact => 1, authtoken=>$auth);
842 return $e->event unless $e->checkauth;
843 my ($mbts) = $U->fetch_mbts($id, $e);
845 my $user = $e->retrieve_actor_user($mbts->usr)
846 or return $e->die_event;
848 return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
854 __PACKAGE__->register_method(
855 method => 'desk_payments',
856 api_name => 'open-ils.circ.money.org_unit.desk_payments'
859 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
860 my $e = new_editor(authtoken=>$auth);
861 return $e->event unless $e->checkauth;
862 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
863 my $data = $U->storagereq(
864 'open-ils.storage.money.org_unit.desk_payments.atomic',
865 $org, $start_date, $end_date );
867 $_->workstation( $_->workstation->name ) for(@$data);
872 __PACKAGE__->register_method(
873 method => 'user_payments',
874 api_name => 'open-ils.circ.money.org_unit.user_payments'
878 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
879 my $e = new_editor(authtoken=>$auth);
880 return $e->event unless $e->checkauth;
881 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
882 my $data = $U->storagereq(
883 'open-ils.storage.money.org_unit.user_payments.atomic',
884 $org, $start_date, $end_date );
887 $e->retrieve_actor_card($_->usr->card)->barcode);
889 $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
895 __PACKAGE__->register_method(
896 method => 'retrieve_credit_payable_balance',
897 api_name => 'open-ils.circ.credit.payable_balance.retrieve',
900 desc => q/Returns the total amount the patron can pay via credit card/,
902 { desc => 'Authentication token', type => 'string' },
903 { desc => 'User id', type => 'number' }
905 return => { desc => 'The ID of the new provider' }
909 sub retrieve_credit_payable_balance {
910 my ( $self, $conn, $auth, $user_id ) = @_;
911 my $e = new_editor(authtoken => $auth);
912 return $e->event unless $e->checkauth;
914 my $user = $e->retrieve_actor_user($user_id)
917 if($e->requestor->id != $user_id) {
918 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou)
921 my $circ_orgs = $e->json_query({
922 "select" => {circ => ["circ_lib"]},
924 "where" => {usr => $user_id, xact_finish => undef},
928 my $groc_orgs = $e->json_query({
929 "select" => {mg => ["billing_location"]},
931 "where" => {usr => $user_id, xact_finish => undef},
936 for my $org ( @$circ_orgs, @$groc_orgs ) {
937 my $o = $org->{billing_location};
938 $o = $org->{circ_lib} unless $o;
939 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.
940 $hash{$o} = $U->ou_ancestor_setting_value($o, 'credit.payments.allow', $e);
943 my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
944 $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
947 OpenILS::Application::AppUtils->simplereq('open-ils.actor',
948 'open-ils.actor.user.transactions.have_charge', $auth, $user_id);
952 for my $xact (@$xact_summaries) {
954 # make two lists and grab them in batch XXX
955 if ( $xact->xact_type eq 'circulation' ) {
956 my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
957 next unless grep { $_ == $circ->circ_lib } @credit_orgs;
959 } elsif ($xact->xact_type eq 'grocery') {
960 my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
961 next unless grep { $_ == $bill->billing_location } @credit_orgs;
962 } elsif ($xact->xact_type eq 'reservation') {
963 my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
964 next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
966 $sum += $xact->balance_owed();